// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
 **********************************************************************
 * Copyright (c) 2004-2016, International Business Machines
 * Corporation and others.  All Rights Reserved.
 **********************************************************************
 * Author: Alan Liu
 * Created: January 14 2004
 * Since: ICU 2.8
 **********************************************************************
 */
package com.ibm.icu.dev.test.util;

import com.ibm.icu.dev.test.CoreTestFmwk;
import com.ibm.icu.dev.test.TestUtil;
import com.ibm.icu.dev.test.TestUtil.JavaVendor;
import com.ibm.icu.text.DateFormat;
import com.ibm.icu.text.DecimalFormat;
import com.ibm.icu.text.DisplayContext;
import com.ibm.icu.text.LocaleDisplayNames;
import com.ibm.icu.text.LocaleDisplayNames.DialectHandling;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.NumberFormat.SimpleNumberFormatFactory;
import com.ibm.icu.text.SimpleDateFormat;
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.IllformedLocaleException;
import com.ibm.icu.util.LocaleData;
import com.ibm.icu.util.LocalePriorityList;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.ULocale.Builder;
import com.ibm.icu.util.ULocale.Category;
import com.ibm.icu.util.ULocale.Minimize;
import com.ibm.icu.util.UResourceBundle;
import com.ibm.icu.util.VersionInfo;
import java.io.BufferedReader;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(JUnitParamsRunner.class)
public class ULocaleTest extends CoreTestFmwk {

    // Ticket #8078 and #11674
    private static final boolean JAVA7_OR_LATER =
            TestUtil.getJavaVendor() == JavaVendor.Android || TestUtil.getJavaVersion() >= 7;

    @Test
    public void TestCalendar() {
        // TODO The CalendarFactory mechanism is not public,
        // so we can't test it yet.  If it becomes public,
        // enable this code.

        // class CFactory implements CalendarFactory {
        //     Locale loc;
        //     Calendar proto;
        //     public CFactory(Locale locale, Calendar prototype) {
        //         loc = locale;
        //         proto = prototype;
        //     }
        //     public Calendar create(TimeZone tz, Locale locale) {
        //         // ignore tz -- not relevant to this test
        //         return locale.equals(loc) ?
        //             (Calendar) proto.clone() : null;
        //     }
        //     public String factoryName() {
        //         return "CFactory";
        //     }
        // };

        checkService(
                "en_US_BROOKLYN",
                new ServiceFacade() {
                    @Override
                    public Object create(ULocale req) {
                        return Calendar.getInstance(req);
                    }
                    // }, null, new Registrar() {
                    //     public Object register(ULocale loc, Object prototype) {
                    //         CFactory f = new CFactory(loc, (Calendar) prototype);
                    //         return Calendar.register(f, loc);
                    //     }
                    //     public boolean unregister(Object key) {
                    //         return Calendar.unregister(key);
                    //     }
                });
    }

    // Currency getLocale API is obsolete in 3.2.  Since it now returns ULocale.ROOT,
    // and this is not equal to the requested locale zh_TW_TAIPEI, the
    // checkService call would always fail.  So we now omit the test.
    /*
    @Test
    public void TestCurrency() {
        checkService("zh_TW_TAIPEI", new ServiceFacade() {
                public Object create(ULocale req) {
                    return Currency.getInstance(req);
                }
            }, null, new Registrar() {
                    public Object register(ULocale loc, Object prototype) {
                        return Currency.registerInstance((Currency) prototype, loc);
                    }
                    public boolean unregister(Object key) {
                        return Currency.unregister(key);
                    }
                });
    }
     */

    @Test
    public void TestDateFormat() {
        checkService(
                "de_CH_ZURICH",
                new ServiceFacade() {
                    @Override
                    public Object create(ULocale req) {
                        return DateFormat.getDateInstance(DateFormat.DEFAULT, req);
                    }
                },
                new Subobject() {
                    @Override
                    public Object get(Object parent) {
                        return ((SimpleDateFormat) parent).getDateFormatSymbols();
                    }
                },
                null);
    }

    @Test
    public void TestNumberFormat() {
        class NFactory extends SimpleNumberFormatFactory {
            NumberFormat proto;
            ULocale locale;

            public NFactory(ULocale loc, NumberFormat fmt) {
                super(loc);
                this.locale = loc;
                this.proto = fmt;
            }

            @Override
            public NumberFormat createFormat(ULocale loc, int formatType) {
                return locale.equals(loc) ? proto.clone() : (NumberFormat) null;
            }
        }

        checkService(
                "fr_FR_NICE",
                new ServiceFacade() {
                    @Override
                    public Object create(ULocale req) {
                        return NumberFormat.getInstance(req);
                    }
                },
                new Subobject() {
                    @Override
                    public Object get(Object parent) {
                        return ((DecimalFormat) parent).getDecimalFormatSymbols();
                    }
                },
                new Registrar() {
                    @Override
                    public Object register(ULocale loc, Object prototype) {
                        NFactory f = new NFactory(loc, (NumberFormat) prototype);
                        return NumberFormat.registerFactory(f);
                    }

                    @Override
                    public boolean unregister(Object key) {
                        return NumberFormat.unregister(key);
                    }
                });
    }

    @Test
    public void TestSetULocaleKeywords() {
        ULocale uloc = new ULocale("en_Latn_US");
        uloc = uloc.setKeywordValue("Foo", "FooValue");
        if (!"en_Latn_US@foo=FooValue".equals(uloc.getName())) {
            errln("failed to add foo keyword, got: " + uloc.getName());
        }
        uloc = uloc.setKeywordValue("Bar", "BarValue");
        if (!"en_Latn_US@bar=BarValue;foo=FooValue".equals(uloc.getName())) {
            errln("failed to add bar keyword, got: " + uloc.getName());
        }
        uloc = uloc.setKeywordValue("BAR", "NewBarValue");
        if (!"en_Latn_US@bar=NewBarValue;foo=FooValue".equals(uloc.getName())) {
            errln("failed to change bar keyword, got: " + uloc.getName());
        }
        uloc = uloc.setKeywordValue("BaR", null);
        if (!"en_Latn_US@foo=FooValue".equals(uloc.getName())) {
            errln("failed to delete bar keyword, got: " + uloc.getName());
        }
        uloc = uloc.setKeywordValue(null, null);
        if (!"en_Latn_US".equals(uloc.getName())) {
            errln("failed to delete all keywords, got: " + uloc.getName());
        }
    }

    /*
     * ticket#5060
     */
    @Test
    public void TestJavaLocaleCompatibility() {
        Locale backupDefault = Locale.getDefault();
        ULocale orgUlocDefault = ULocale.getDefault();

        // Java Locale for ja_JP with Japanese calendar
        Locale jaJPJP = new Locale("ja", "JP", "JP");
        Locale jaJP = new Locale("ja", "JP");
        // Java Locale for th_TH with Thai digits
        Locale thTHTH = new Locale("th", "TH", "TH");

        Calendar cal = Calendar.getInstance(jaJPJP);
        String caltype = cal.getType();
        if (!caltype.equals("japanese")) {
            errln("FAIL: Invalid calendar type: " + caltype + " /expected: japanese");
        }

        cal = Calendar.getInstance(jaJP);
        caltype = cal.getType();
        if (!caltype.equals("gregorian")) {
            errln("FAIL: Invalid calendar type: " + caltype + " /expected: gregorian");
        }

        // Default locale
        Locale.setDefault(jaJPJP);
        ULocale defUloc = ULocale.getDefault();
        if (JAVA7_OR_LATER) {
            if (!defUloc.toString().equals("ja_JP_JP@calendar=japanese")) {
                errln(
                        "FAIL: Invalid default ULocale: "
                                + defUloc
                                + " /expected: ja_JP_JP@calendar=japanese");
            }
        } else {
            if (!defUloc.toString().equals("ja_JP@calendar=japanese")) {
                errln(
                        "FAIL: Invalid default ULocale: "
                                + defUloc
                                + " /expected: ja_JP@calendar=japanese");
            }
        }
        // Check calendar type
        cal = Calendar.getInstance();
        caltype = cal.getType();
        if (!caltype.equals("japanese")) {
            errln("FAIL: Invalid calendar type: " + caltype + " /expected: japanese");
        }
        Locale.setDefault(backupDefault);

        // Set default via ULocale
        ULocale ujaJP_calJP = new ULocale("ja_JP@calendar=japanese");
        ULocale.setDefault(ujaJP_calJP);
        if (!JAVA7_OR_LATER && !Locale.getDefault().equals(jaJPJP)) {
            errln(
                    "FAIL: ULocale#setDefault failed to set Java Locale ja_JP_JP /actual: "
                            + Locale.getDefault());
        }
        // Ticket#6672 - missing keywords
        defUloc = ULocale.getDefault();
        if (!defUloc.equals(ujaJP_calJP)) {
            errln(
                    "FAIL: ULocale#getDefault returned "
                            + defUloc
                            + " /expected: ja_JP@calendar=japanese");
        }
        // Set a incompatible base locale via Locale#setDefault
        Locale.setDefault(Locale.US);
        defUloc = ULocale.getDefault();
        if (defUloc.equals(ujaJP_calJP)) {
            errln(
                    "FAIL: ULocale#getDefault returned "
                            + defUloc
                            + " /expected: "
                            + ULocale.forLocale(Locale.US));
        }

        Locale.setDefault(backupDefault);

        // We also want to map ICU locale ja@calendar=japanese to Java ja_JP_JP
        ULocale.setDefault(new ULocale("ja@calendar=japanese"));
        if (!JAVA7_OR_LATER && !Locale.getDefault().equals(jaJPJP)) {
            errln(
                    "FAIL: ULocale#setDefault failed to set Java Locale ja_JP_JP /actual: "
                            + Locale.getDefault());
        }
        Locale.setDefault(backupDefault);

        // Java no_NO_NY
        Locale noNONY = new Locale("no", "NO", "NY");
        Locale.setDefault(noNONY);
        defUloc = ULocale.getDefault();
        if (!defUloc.toString().equals("nn_NO")) {
            errln("FAIL: Invalid default ULocale: " + defUloc + " /expected: nn_NO");
        }
        Locale.setDefault(backupDefault);

        // Java th_TH_TH -> ICU th_TH@numbers=thai
        ULocale.setDefault(new ULocale("th@numbers=thai"));
        if (!JAVA7_OR_LATER && !Locale.getDefault().equals(thTHTH)) {
            errln(
                    "FAIL: ULocale#setDefault failed to set Java Locale th_TH_TH /actual: "
                            + Locale.getDefault());
        }
        Locale.setDefault(backupDefault);

        // Set default via ULocale
        ULocale.setDefault(new ULocale("nn_NO"));
        if (!JAVA7_OR_LATER && !Locale.getDefault().equals(noNONY)) {
            errln(
                    "FAIL: ULocale#setDefault failed to set Java Locale no_NO_NY /actual: "
                            + Locale.getDefault());
        }
        Locale.setDefault(backupDefault);

        // We also want to map ICU locale nn to Java no_NO_NY
        ULocale.setDefault(new ULocale("nn"));
        if (!JAVA7_OR_LATER && !Locale.getDefault().equals(noNONY)) {
            errln(
                    "FAIL: ULocale#setDefault failed to set Java Locale no_NO_NY /actual: "
                            + Locale.getDefault());
        }
        Locale.setDefault(backupDefault);

        // Make sure default ULocale is restored
        if (!ULocale.getDefault().equals(orgUlocDefault)) {
            errln(
                    "FAIL: Original default ULocale is not restored - "
                            + ULocale.getDefault()
                            + ", expected(orginal) - "
                            + orgUlocDefault);
        }
    }

    // ================= Infrastructure =================

    /**
     * Compare two locale IDs. If they are equal, return 0. If `string' starts with `prefix' plus an
     * additional element, that is, string == prefix + '_' + x, then return 1. Otherwise return a
     * value < 0.
     */
    static int loccmp(String string, String prefix) {
        int slen = string.length(), plen = prefix.length();
        /* 'root' is "less than" everything */
        if (prefix.equals("root")) {
            return string.equals("root") ? 0 : 1;
        }
        // ON JAVA (only -- not on C -- someone correct me if I'm wrong)
        // consider "" to be an alternate name for "root".
        if (plen == 0) {
            return slen == 0 ? 0 : 1;
        }
        if (!string.startsWith(prefix)) return -1; /* mismatch */
        if (slen == plen) return 0;
        if (string.charAt(plen) == '_') return 1;
        return -2; /* false match, e.g. "en_USX" cmp "en_US" */
    }

    /**
     * Check the relationship between requested locales, and report problems. The caller specifies
     * the expected relationships between requested and valid (expReqValid) and between valid and
     * actual (expValidActual). Possible values are: "gt" strictly greater than, e.g., en_US > en
     * "ge" greater or equal, e.g., en >= en "eq" equal, e.g., en == en
     */
    void checklocs(
            String label,
            String req,
            Locale validLoc,
            Locale actualLoc,
            String expReqValid,
            String expValidActual) {
        String valid = validLoc.toString();
        String actual = actualLoc.toString();
        int reqValid = loccmp(req, valid);
        int validActual = loccmp(valid, actual);
        boolean reqOK =
                (expReqValid.equals("gt") && reqValid > 0)
                        || (expReqValid.equals("ge") && reqValid >= 0)
                        || (expReqValid.equals("eq") && reqValid == 0);
        boolean valOK =
                (expValidActual.equals("gt") && validActual > 0)
                        || (expValidActual.equals("ge") && validActual >= 0)
                        || (expValidActual.equals("eq") && validActual == 0);
        if (reqOK && valOK) {
            logln("Ok: " + label + "; req=" + req + ", valid=" + valid + ", actual=" + actual);
        } else {
            errln(
                    "FAIL: "
                            + label
                            + "; req="
                            + req
                            + ", valid="
                            + valid
                            + ", actual="
                            + actual
                            + (reqOK ? "" : "\n  req !" + expReqValid + " valid")
                            + (valOK ? "" : "\n  val !" + expValidActual + " actual"));
        }
    }

    /**
     * Interface used by checkService defining a protocol to create an object, given a requested
     * locale.
     */
    interface ServiceFacade {
        Object create(ULocale requestedLocale);
    }

    /**
     * Interface used by checkService defining a protocol to get a contained subobject, given its
     * parent object.
     */
    interface Subobject {
        Object get(Object parent);
    }

    /**
     * Interface used by checkService defining a protocol to register and unregister a service
     * object prototype.
     */
    interface Registrar {
        Object register(ULocale loc, Object prototype);

        boolean unregister(Object key);
    }

    /**
     * Use reflection to call getLocale() on the given object to determine both the valid and the
     * actual locale. Verify these for correctness.
     */
    void checkObject(
            String requestedLocale, Object obj, String expReqValid, String expValidActual) {
        Class<?>[] getLocaleParams = new Class<?>[] {ULocale.Type.class};
        try {
            Class<?> cls = obj.getClass();
            Method getLocale = cls.getMethod("getLocale", getLocaleParams);
            ULocale valid = (ULocale) getLocale.invoke(obj, new Object[] {ULocale.VALID_LOCALE});
            ULocale actual = (ULocale) getLocale.invoke(obj, new Object[] {ULocale.ACTUAL_LOCALE});
            checklocs(
                    cls.getName(),
                    requestedLocale,
                    valid.toLocale(),
                    actual.toLocale(),
                    expReqValid,
                    expValidActual);
        }

        // Make the following exceptions _specific_ -- do not
        // catch(Exception), since that will catch the exception
        // that errln throws.
        catch (NoSuchMethodException e1) {
            // no longer an error, Currency has no getLocale
            // errln("FAIL: reflection failed: " + e1);
        } catch (SecurityException e2) {
            errln("FAIL: reflection failed: " + e2);
        } catch (IllegalAccessException e3) {
            errln("FAIL: reflection failed: " + e3);
        } catch (IllegalArgumentException e4) {
            errln("FAIL: reflection failed: " + e4);
        } catch (InvocationTargetException e5) {
            // no longer an error, Currency has no getLocale
            // errln("FAIL: reflection failed: " + e5);
        }
    }

    /**
     * Verify the correct getLocale() behavior for the given service.
     *
     * @param requestedLocale the locale to request. This MUST BE FAKE. In other words, it should be
     *     something like en_US_FAKEVARIANT so this method can verify correct fallback behavior.
     * @param svc a factory object that can create the object to be tested. This isn't necessary
     *     here (one could just pass in the object) but is required for the overload of this method
     *     that takes a Registrar.
     */
    void checkService(String requestedLocale, ServiceFacade svc) {
        checkService(requestedLocale, svc, null, null);
    }

    /**
     * Verify the correct getLocale() behavior for the given service.
     *
     * @param requestedLocale the locale to request. This MUST BE FAKE. In other words, it should be
     *     something like en_US_FAKEVARIANT so this method can verify correct fallback behavior.
     * @param svc a factory object that can create the object to be tested.
     * @param sub an object that can be used to retrieve a subobject which should also be tested.
     *     May be null.
     * @param reg an object that supplies the registration and unregistration functionality to be
     *     tested. May be null.
     */
    void checkService(String requestedLocale, ServiceFacade svc, Subobject sub, Registrar reg) {
        ULocale req = new ULocale(requestedLocale);
        Object obj = svc.create(req);
        checkObject(requestedLocale, obj, "gt", "ge");
        if (sub != null) {
            Object subobj = sub.get(obj);
            checkObject(requestedLocale, subobj, "gt", "ge");
        }
        if (reg != null) {
            logln("Info: Registering service");
            Object key = reg.register(req, obj);
            Object objReg = svc.create(req);
            checkObject(requestedLocale, objReg, "eq", "eq");
            if (sub != null) {
                Object subobj = sub.get(obj);
                // Assume subobjects don't come from services, so
                // their metadata should be structured normally.
                checkObject(requestedLocale, subobj, "gt", "ge");
            }
            logln("Info: Unregistering service");
            if (!reg.unregister(key)) {
                errln("FAIL: unregister failed");
            }
            Object objUnreg = svc.create(req);
            checkObject(requestedLocale, objUnreg, "gt", "ge");
        }
    }

    private static final int LOCALE_SIZE = 9;
    private static final String[][] rawData2 =
            new String[][] {
                /* language code */
                {"en", "fr", "ca", "el", "no", "zh", "de", "es", "ja"},
                /* script code */
                {"", "", "", "", "", "Hans", "", "", ""},
                /* country code */
                {"US", "FR", "ES", "GR", "NO", "CN", "DE", "", "JP"},
                /* variant code */
                {"", "", "", "", "NY", "", "", "", ""},
                /* full name */
                {
                    "en_US", "fr_FR", "ca_ES",
                    "el_GR", "no_NO_NY", "zh_Hans_CN",
                    "de_DE@collation=phonebook", "es@collation=traditional",
                            "ja_JP@calendar=japanese"
                },
                /* ISO-3 language */
                {"eng", "fra", "cat", "ell", "nor", "zho", "deu", "spa", "jpn"},
                /* ISO-3 country */
                {"USA", "FRA", "ESP", "GRC", "NOR", "CHN", "DEU", "", "JPN"},
                /* LCID */
                {"409", "40c", "403", "408", "814", "804", "407", "a", "411"},

                /* display language (English) */
                {
                    "English",
                    "French",
                    "Catalan",
                    "Greek",
                    "Norwegian",
                    "Chinese",
                    "German",
                    "Spanish",
                    "Japanese"
                },
                /* display script code (English) */
                {"", "", "", "", "", "Simplified Han", "", "", ""},
                /* display country (English) */
                {
                    "United States",
                    "France",
                    "Spain",
                    "Greece",
                    "Norway",
                    "China",
                    "Germany",
                    "",
                    "Japan"
                },
                /* display variant (English) */
                {"", "", "", "", "NY", "", "", "", ""},
                /* display name (English) */
                {
                    "English (United States)", "French (France)", "Catalan (Spain)",
                    "Greek (Greece)", "Norwegian (Norway, NY)", "Chinese (Simplified Han, China)",
                    "German (Germany, Collation=Phonebook Sort Order)",
                            "Spanish (Collation=Traditional)",
                            "Japanese (Japan, Calendar=Japanese Calendar)"
                },

                /* display language (French) */
                {
                    "anglais",
                    "fran\\u00E7ais",
                    "catalan",
                    "grec",
                    "norv\\u00E9gien",
                    "chinois",
                    "allemand",
                    "espagnol",
                    "japonais"
                },
                /* display script code (French) */
                {"", "", "", "", "", "Hans", "", "", ""},
                /* display country (French) */
                {
                    "\\u00C9tats-Unis",
                    "France",
                    "Espagne",
                    "Gr\\u00E8ce",
                    "Norv\\u00E8ge",
                    "Chine",
                    "Allemagne",
                    "",
                    "Japon"
                },
                /* display variant (French) */
                {"", "", "", "", "NY", "", "", "", ""},
                /* display name (French) */
                {
                    "anglais (\\u00C9tats-Unis)", "fran\\u00E7ais (France)", "catalan (Espagne)",
                    "grec (Gr\\u00E8ce)", "norv\\u00E9gien (Norv\\u00E8ge, NY)",
                            "chinois (Hans, Chine)",
                    "allemand (Allemagne, Ordonnancement=Ordre de l'annuaire)",
                            "espagnol (Ordonnancement=Ordre traditionnel)",
                            "japonais (Japon, Calendrier=Calendrier japonais)"
                },

                /* display language (Catalan) */
                {
                    "angl\\u00E8s",
                    "franc\\u00E8s",
                    "catal\\u00E0",
                    "grec",
                    "noruec",
                    "xin\\u00E9s",
                    "alemany",
                    "espanyol",
                    "japon\\u00E8s"
                },
                /* display script code (Catalan) */
                {"", "", "", "", "", "Hans", "", "", ""},
                /* display country (Catalan) */
                {
                    "Estats Units",
                    "Fran\\u00E7a",
                    "Espanya",
                    "Gr\\u00E8cia",
                    "Noruega",
                    "Xina",
                    "Alemanya",
                    "",
                    "Jap\\u00F3"
                },
                /* display variant (Catalan) */
                {"", "", "", "", "NY", "", "", "", ""},
                /* display name (Catalan) */
                {
                    "angl\\u00E8s (Estats Units)", "franc\\u00E8s (Fran\\u00E7a)",
                            "catal\\u00E0 (Espanya)",
                    "grec (Gr\\u00E8cia)", "noruec (Noruega, NY)", "xin\\u00E9s (Hans, Xina)",
                    "alemany (Alemanya, COLLATION=PHONEBOOK)", "espanyol (COLLATION=TRADITIONAL)",
                            "japon\\u00E8s (Jap\\u00F3, CALENDAR=JAPANESE)"
                },

                /* display language (Greek) */
                {
                    "\\u0391\\u03b3\\u03b3\\u03bb\\u03b9\\u03ba\\u03ac",
                    "\\u0393\\u03b1\\u03bb\\u03bb\\u03b9\\u03ba\\u03ac",
                    "\\u039a\\u03b1\\u03c4\\u03b1\\u03bb\\u03b1\\u03bd\\u03b9\\u03ba\\u03ac",
                    "\\u0395\\u03bb\\u03bb\\u03b7\\u03bd\\u03b9\\u03ba\\u03ac",
                    "\\u039d\\u03bf\\u03c1\\u03b2\\u03b7\\u03b3\\u03b9\\u03ba\\u03ac",
                    "\\u039A\\u03B9\\u03BD\\u03B5\\u03B6\\u03B9\\u03BA\\u03AC",
                    "\\u0393\\u03B5\\u03C1\\u03BC\\u03B1\\u03BD\\u03B9\\u03BA\\u03AC",
                    "\\u0399\\u03C3\\u03C0\\u03B1\\u03BD\\u03B9\\u03BA\\u03AC",
                    "\\u0399\\u03B1\\u03C0\\u03C9\\u03BD\\u03B9\\u03BA\\u03AC"
                },
                /* display script code (Greek) */
                {"", "", "", "", "", "Hans", "", "", ""},
                /* display country (Greek) */
                {
                    "\\u0397\\u03bd\\u03c9\\u03bc\\u03ad\\u03bd\\u03b5\\u03c2 \\u03a0\\u03bf\\u03bb\\u03b9\\u03c4\\u03b5\\u03af\\u03b5\\u03c2",
                    "\\u0393\\u03b1\\u03bb\\u03bb\\u03af\\u03b1",
                    "\\u0399\\u03c3\\u03c0\\u03b1\\u03bd\\u03af\\u03b1",
                    "\\u0395\\u03bb\\u03bb\\u03ac\\u03b4\\u03b1",
                    "\\u039d\\u03bf\\u03c1\\u03b2\\u03b7\\u03b3\\u03af\\u03b1",
                    "\\u039A\\u03AF\\u03BD\\u03B1",
                    "\\u0393\\u03B5\\u03C1\\u03BC\\u03B1\\u03BD\\u03AF\\u03B1",
                    "",
                    "\\u0399\\u03B1\\u03C0\\u03C9\\u03BD\\u03AF\\u03B1"
                },
                /* display variant (Greek) */
                {
                    "", "", "", "", "NY", "", "", "", ""
                }, /* TODO: currently there is no translation for NY in Greek fix this test when we have it */
                /* display name (Greek) */
                {
                    "\\u0391\\u03b3\\u03b3\\u03bb\\u03b9\\u03ba\\u03ac (\\u0397\\u03bd\\u03c9\\u03bc\\u03ad\\u03bd\\u03b5\\u03c2 \\u03a0\\u03bf\\u03bb\\u03b9\\u03c4\\u03b5\\u03af\\u03b5\\u03c2)",
                    "\\u0393\\u03b1\\u03bb\\u03bb\\u03b9\\u03ba\\u03ac (\\u0393\\u03b1\\u03bb\\u03bb\\u03af\\u03b1)",
                    "\\u039a\\u03b1\\u03c4\\u03b1\\u03bb\\u03b1\\u03bd\\u03b9\\u03ba\\u03ac (\\u0399\\u03c3\\u03c0\\u03b1\\u03bd\\u03af\\u03b1)",
                    "\\u0395\\u03bb\\u03bb\\u03b7\\u03bd\\u03b9\\u03ba\\u03ac (\\u0395\\u03bb\\u03bb\\u03ac\\u03b4\\u03b1)",
                    "\\u039d\\u03bf\\u03c1\\u03b2\\u03b7\\u03b3\\u03b9\\u03ba\\u03ac (\\u039d\\u03bf\\u03c1\\u03b2\\u03b7\\u03b3\\u03af\\u03b1, NY)",
                    "\\u039A\\u03B9\\u03BD\\u03B5\\u03B6\\u03B9\\u03BA\\u03AC (Hans, \\u039A\\u03AF\\u03BD\\u03B1)",
                    "\\u0393\\u03B5\\u03C1\\u03BC\\u03B1\\u03BD\\u03B9\\u03BA\\u03AC (\\u0393\\u03B5\\u03C1\\u03BC\\u03B1\\u03BD\\u03AF\\u03B1, COLLATION=PHONEBOOK)",
                    "\\u0399\\u03C3\\u03C0\\u03B1\\u03BD\\u03B9\\u03BA\\u03AC (COLLATION=TRADITIONAL)",
                    "\\u0399\\u03B1\\u03C0\\u03C9\\u03BD\\u03B9\\u03BA\\u03AC (\\u0399\\u03B1\\u03C0\\u03C9\\u03BD\\u03AF\\u03B1, CALENDAR=JAPANESE)"
                }
            };
    //    private static final int ENGLISH = 0;
    //    private static final int FRENCH = 1;
    //    private static final int CATALAN = 2;
    //    private static final int GREEK = 3;
    //    private static final int NORWEGIAN = 4;
    private static final int LANG = 0;
    private static final int SCRIPT = 1;
    private static final int CTRY = 2;
    private static final int VAR = 3;
    private static final int NAME = 4;

    //    private static final int LANG3 = 5;
    //    private static final int CTRY3 = 6;
    //    private static final int LCID = 7;
    //    private static final int DLANG_EN = 8;
    //    private static final int DSCRIPT_EN = 9;
    //    private static final int DCTRY_EN = 10;
    //    private static final int DVAR_EN = 11;
    //    private static final int DNAME_EN = 12;
    //    private static final int DLANG_FR = 13;
    //    private static final int DSCRIPT_FR = 14;
    //    private static final int DCTRY_FR = 15;
    //    private static final int DVAR_FR = 16;
    //    private static final int DNAME_FR = 17;
    //    private static final int DLANG_CA = 18;
    //    private static final int DSCRIPT_CA = 19;
    //    private static final int DCTRY_CA = 20;
    //    private static final int DVAR_CA = 21;
    //    private static final int DNAME_CA = 22;
    //    private static final int DLANG_EL = 23;
    //    private static final int DSCRIPT_EL = 24;
    //    private static final int DCTRY_EL = 25;
    //    private static final int DVAR_EL = 26;
    //    private static final int DNAME_EL = 27;

    @Test
    public void TestBasicGetters() {
        int i;
        logln("Testing Basic Getters\n");
        for (i = 0; i < LOCALE_SIZE; i++) {
            String testLocale = (rawData2[NAME][i]);
            logln("Testing " + testLocale + ".....\n");

            String lang = ULocale.getLanguage(testLocale);
            if (0 != lang.compareTo(rawData2[LANG][i])) {
                errln("  Language code mismatch: " + lang + " versus " + rawData2[LANG][i]);
            }

            String ctry = ULocale.getCountry(testLocale);
            if (0 != ctry.compareTo(rawData2[CTRY][i])) {
                errln("  Country code mismatch: " + ctry + " versus " + rawData2[CTRY][i]);
            }

            String var = ULocale.getVariant(testLocale);
            if (0 != var.compareTo(rawData2[VAR][i])) {
                errln("  Variant code mismatch: " + var + " versus " + rawData2[VAR][i]);
            }

            String name = ULocale.getName(testLocale);
            if (0 != name.compareTo(rawData2[NAME][i])) {
                errln("  Name mismatch: " + name + " versus " + rawData2[NAME][i]);
            }
        }
    }

    @Test
    public void TestPrefixes() {
        // POSIX ids are no longer handled by getName, so POSIX failures are ignored
        final String[][] testData =
                new String[][] {
                    /* null canonicalize() column means "expect same as getName()" */
                    {"sv", "", "FI", "AL", "sv-fi-al", "sv_FI_AL", null},
                    {"en", "", "GB", "", "en-gb", "en_GB", null},
                    {
                        "i-hakka",
                        "",
                        "MT",
                        "XEMXIJA",
                        "i-hakka_MT_XEMXIJA",
                        "i-hakka_MT_XEMXIJA",
                        null
                    },
                    {"i-hakka", "", "CN", "", "i-hakka_CN", "i-hakka_CN", null},
                    {"i-hakka", "", "MX", "", "I-hakka_MX", "i-hakka_MX", null},
                    {
                        "x-klingon",
                        "",
                        "US",
                        "SANJOSE",
                        "X-KLINGON_us_SANJOSE",
                        "x-klingon_US_SANJOSE",
                        null
                    },
                    {"de", "", "", "1901", "de-1901", "de__1901", null},
                    {"mr", "", "", "", "mr.utf8", "mr.utf8", "mr"},
                    {"de", "", "TV", "", "de-tv.koi8r", "de_TV.koi8r", "de_TV"},
                    {
                        "x-piglatin",
                        "",
                        "ML",
                        "",
                        "x-piglatin_ML.MBE",
                        "x-piglatin_ML.MBE",
                        "x-piglatin_ML"
                    }, /* Multibyte English */
                    {
                        "i-cherokee",
                        "",
                        "US",
                        "",
                        "i-Cherokee_US.utf7",
                        "i-cherokee_US.utf7",
                        "i-cherokee_US"
                    },
                    {
                        "x-filfli",
                        "",
                        "MT",
                        "FILFLA",
                        "x-filfli_MT_FILFLA.gb-18030",
                        "x-filfli_MT_FILFLA.gb-18030",
                        "x-filfli_MT_FILFLA"
                    },
                    {"no", "", "NO", "NY_B", "no-no-ny.utf32@B", "no_NO_NY.utf32@B", "no_NO_NY_B"},
                    {"no", "", "NO", "B", "no-no.utf32@B", "no_NO.utf32@B", "no_NO_B"},
                    {"no", "", "", "NY", "no__ny", "no__NY", "no__NY"},
                    {"no", "", "", "NY", "no@ny", "no@ny", "no__NY"},
                    {"el", "Latn", "", "", "el-latn", "el_Latn", null},
                    {"en", "Cyrl", "RU", "", "en-cyrl-ru", "en_Cyrl_RU", null},
                    {"qq", "Qqqq", "QQ", "QQ", "qq_Qqqq_QQ_QQ", "qq_Qqqq_QQ_QQ", null},
                    {"qq", "Qqqq", "", "QQ", "qq_Qqqq__QQ", "qq_Qqqq__QQ", null},
                    {
                        "ab", "Cdef", "GH", "IJ", "ab_cdef_gh_ij", "ab_Cdef_GH_IJ", null
                    }, /* total garbage */

                    // odd cases
                    {"", "", "", "", "@FOO=bar", "@foo=bar", null},
                    {"", "", "", "", "_@FOO=bar", "@foo=bar", null},
                    {"", "", "", "", "__@FOO=bar", "@foo=bar", null},
                    {
                        "", "", "", "FOO", "__foo@FOO=bar", "__FOO@foo=bar", null
                    }, // we have some of these prefixes

                    // Before ICU 64, ICU locale canonicalization had some additional mappings.
                    // They were removed for ICU-20187 "drop support for long-obsolete locale ID
                    // variants".
                    // The following now use standard canonicalization.
                    {
                        "zh",
                        "Hant",
                        "TW",
                        "STROKE",
                        "zh-hant_TW_STROKE",
                        "zh_Hant_TW_STROKE",
                        "zh_Hant_TW_STROKE"
                    },
                    {
                        "zh",
                        "Hant",
                        "CN",
                        "STROKE",
                        "zh-hant_CN_STROKE",
                        "zh_Hant_CN_STROKE",
                        "zh_Hant_CN_STROKE"
                    },
                    {
                        "zh",
                        "Hant",
                        "TW",
                        "PINYIN",
                        "zh-hant_TW_PINYIN",
                        "zh_Hant_TW_PINYIN",
                        "zh_Hant_TW_PINYIN"
                    },
                };

        String loc, buf, buf1;
        final String[] testTitles = {
            "ULocale.getLanguage()",
            "ULocale.getScript()",
            "ULocale.getCountry()",
            "ULocale.getVariant()",
            "name",
            "ULocale.getName()",
            "canonicalize()",
        };
        ULocale uloc;

        for (int row = 0; row < testData.length; row++) {
            loc = testData[row][NAME];
            logln("Test #" + row + ": " + loc);

            uloc = new ULocale(loc);

            for (int n = 0; n <= (NAME + 2); n++) {
                if (n == NAME) continue;

                switch (n) {
                    case LANG:
                        buf = ULocale.getLanguage(loc);
                        buf1 = uloc.getLanguage();
                        break;

                    case SCRIPT:
                        buf = ULocale.getScript(loc);
                        buf1 = uloc.getScript();
                        break;

                    case CTRY:
                        buf = ULocale.getCountry(loc);
                        buf1 = uloc.getCountry();
                        break;

                    case VAR:
                        buf = ULocale.getVariant(loc);
                        buf1 = buf;
                        break;

                    case NAME + 1:
                        buf = ULocale.getName(loc);
                        buf1 = uloc.getName();
                        break;

                    case NAME + 2:
                        buf = ULocale.canonicalize(loc);
                        buf1 = ULocale.createCanonical(loc).getName();
                        break;

                    default:
                        buf = "**??";
                        buf1 = buf;
                }

                logln("#" + row + ": " + testTitles[n] + " on " + loc + ": -> [" + buf + "]");

                String expected = testData[row][n];
                if (expected == null && n == (NAME + 2)) {
                    expected = testData[row][NAME + 1];
                }

                // ignore POSIX failures in getName, we don't spec behavior in this case
                if (n == NAME + 1 && (expected.indexOf('.') != -1 || expected.indexOf('@') != -1)) {
                    continue;
                }

                if (buf.compareTo(expected) != 0) {
                    errln(
                            "#"
                                    + row
                                    + ": "
                                    + testTitles[n]
                                    + " on "
                                    + loc
                                    + ": -> ["
                                    + buf
                                    + "] (expected '"
                                    + expected
                                    + "'!)");
                }
                if (buf1.compareTo(expected) != 0) {
                    errln(
                            "#"
                                    + row
                                    + ": "
                                    + testTitles[n]
                                    + " on ULocale object "
                                    + loc
                                    + ": -> ["
                                    + buf1
                                    + "] (expected '"
                                    + expected
                                    + "'!)");
                }
            }
        }
    }

    @Test
    public void TestUldnWithGarbage() {
        LocaleDisplayNames ldn =
                LocaleDisplayNames.getInstance(Locale.US, DisplayContext.DIALECT_NAMES);
        String badLocaleID = "english (United States) [w";
        String expectedResult = "english [united states] [w"; // case changed from input
        String result = ldn.localeDisplayName(badLocaleID);
        if (result.compareTo(expectedResult) != 0) {
            errln(
                    "FAIL: LocaleDisplayNames.localeDisplayName(String) for bad locale ID \""
                            + badLocaleID
                            + "\", expected \""
                            + expectedResult
                            + "\", got \""
                            + result
                            + "\"");
        }
        ULocale badLocale = new ULocale(badLocaleID);
        result = ldn.localeDisplayName(badLocale);
        if (result.compareTo(expectedResult) != 0) {
            errln(
                    "FAIL: LocaleDisplayNames.localeDisplayName(ULocale) for bad locale ID \""
                            + badLocaleID
                            + "\", expected \""
                            + expectedResult
                            + "\", got \""
                            + result
                            + "\"");
        }
    }

    @Test
    public void TestObsoleteNames() {
        final String[][] tests =
                new String[][] {
                    /* locale, language3, language2, Country3, country2 */
                    {"eng_USA", "eng", "en", "USA", "US"},
                    {"kok", "kok", "kok", "", ""},
                    {"in", "ind", "in", "", ""},
                    {"id", "ind", "id", "", ""}, /* NO aliasing */
                    {"sh", "srp", "sh", "", ""},
                    {"zz_CS", "", "zz", "SCG", "CS"},
                    {"zz_FX", "", "zz", "FXX", "FX"},
                    {"zz_RO", "", "zz", "ROU", "RO"},
                    {"zz_TP", "", "zz", "TMP", "TP"},
                    {"zz_TL", "", "zz", "TLS", "TL"},
                    {"zz_ZR", "", "zz", "ZAR", "ZR"},
                    {"zz_FXX", "", "zz", "FXX", "FX"}, /* no aliasing. Doesn't go to PS(PSE). */
                    {"zz_ROM", "", "zz", "ROU", "RO"},
                    {"zz_ROU", "", "zz", "ROU", "RO"},
                    {"zz_ZAR", "", "zz", "ZAR", "ZR"},
                    {"zz_TMP", "", "zz", "TMP", "TP"},
                    {"zz_TLS", "", "zz", "TLS", "TL"},
                    {"zz_YUG", "", "zz", "YUG", "YU"},
                    {"mlt_PSE", "mlt", "mt", "PSE", "PS"},
                    {"iw", "heb", "iw", "", ""},
                    {"ji", "yid", "ji", "", ""},
                    {"jw", "jaw", "jw", "", ""},
                    {"sh", "srp", "sh", "", ""},
                    {"mo", "ron", "mo", "", ""},
                    // and some non-obsolete ones
                    {"az", "aze", "az", "", ""},
                    {"cy", "cym", "cy", "", ""},
                    {"hz", "her", "hz", "", ""},
                    {"ky", "kir", "ky", "", ""},
                    {"na", "nau", "na", "", ""},
                    {"sa", "san", "sa", "", ""},
                    {"ts", "tso", "ts", "", ""},
                    {"zu", "zul", "zu", "", ""},
                    {"zz_DG", "", "zz", "DGA", "DG"},
                    {"zz_IC", "", "zz", "XIC", "IC"},
                    {"zz_XK", "", "zz", "XKK", "XK"},
                    // terminator
                    {"", "", "", "", ""}
                };

        for (int i = 0; i < tests.length; i++) {
            String locale = tests[i][0];
            logln("** Testing : " + locale);
            String buff, buff1;
            ULocale uloc = new ULocale(locale);

            buff = ULocale.getISO3Language(locale);
            if (buff.compareTo(tests[i][1]) != 0) {
                errln(
                        "FAIL: ULocale.getISO3Language("
                                + locale
                                + ")=="
                                + buff
                                + ",\t expected "
                                + tests[i][1]);
            } else {
                logln("   ULocale.getISO3Language(" + locale + ")==" + buff);
            }

            buff1 = uloc.getISO3Language();
            if (buff1.compareTo(tests[i][1]) != 0) {
                errln(
                        "FAIL: ULocale.getISO3Language("
                                + locale
                                + ")=="
                                + buff
                                + ",\t expected "
                                + tests[i][1]);
            } else {
                logln("   ULocale.getISO3Language(" + locale + ")==" + buff);
            }

            buff = ULocale.getLanguage(locale);
            if (buff.compareTo(tests[i][2]) != 0) {
                errln(
                        "FAIL: ULocale.getLanguage("
                                + locale
                                + ")=="
                                + buff
                                + ",\t expected "
                                + tests[i][2]);
            } else {
                logln("   ULocale.getLanguage(" + locale + ")==" + buff);
            }

            buff = ULocale.getISO3Country(locale);
            if (buff.compareTo(tests[i][3]) != 0) {
                errln(
                        "FAIL: ULocale.getISO3Country("
                                + locale
                                + ")=="
                                + buff
                                + ",\t expected "
                                + tests[i][3]);
            } else {
                logln("   ULocale.getISO3Country(" + locale + ")==" + buff);
            }

            buff1 = uloc.getISO3Country();
            if (buff1.compareTo(tests[i][3]) != 0) {
                errln(
                        "FAIL: ULocale.getISO3Country("
                                + locale
                                + ")=="
                                + buff
                                + ",\t expected "
                                + tests[i][3]);
            } else {
                logln("   ULocale.getISO3Country(" + locale + ")==" + buff);
            }

            buff = ULocale.getCountry(locale);
            if (buff.compareTo(tests[i][4]) != 0) {
                errln(
                        "FAIL: ULocale.getCountry("
                                + locale
                                + ")=="
                                + buff
                                + ",\t expected "
                                + tests[i][4]);
            } else {
                logln("   ULocale.getCountry(" + locale + ")==" + buff);
            }
        }

        if (ULocale.getLanguage("iw_IL").compareTo(ULocale.getLanguage("he_IL")) == 0) {
            errln("he,iw ULocale.getLanguage mismatch");
        }

        String buff = ULocale.getLanguage("kok_IN");
        if (buff.compareTo("kok") != 0) {
            errln("ULocale.getLanguage(\"kok\") failed. Expected: kok Got: " + buff);
        }
    }

    @Test
    public void TestCanonicalization() {
        final String[][] testCases =
                new String[][] {
                    {"zh@collation=pinyin", "zh@collation=pinyin", "zh@collation=pinyin"},
                    {"zh_CN@collation=pinyin", "zh_CN@collation=pinyin", "zh_CN@collation=pinyin"},
                    {
                        "zh_CN_CA@collation=pinyin",
                        "zh_CN_CA@collation=pinyin",
                        "zh_CN_CA@collation=pinyin"
                    },
                    {"en_US_POSIX", "en_US_POSIX", "en_US_POSIX"},
                    {"hy_AM_REVISED", "hy_AM_REVISED", "hy_AM_REVISED"},
                    {"no_NO_NY", "no_NO_NY", "no_NO_NY" /* not: "nn_NO" [alan ICU3.0] */},
                    {"no@ny", null, "no__NY" /* not: "nn" [alan ICU3.0] */}, /* POSIX ID */
                    {"no-no.utf32@B", null, "no_NO_B"}, /* POSIX ID */
                    {"en-BOONT", "en__BOONT", "en__BOONT"}, /* registered name */
                    {"de-1901", "de__1901", "de__1901"}, /* registered name */
                    {"de-1906", "de__1906", "de__1906"}, /* registered name */
                    // New in CLDR 39 / ICU 69
                    {"nb", "nb", "nb"},

                    /* posix behavior that used to be performed by getName */
                    {"mr.utf8", null, "mr"},
                    {"de-tv.koi8r", null, "de_TV"},
                    {"x-piglatin_ML.MBE", null, "x-piglatin_ML"},
                    {"i-cherokee_US.utf7", null, "i-cherokee_US"},
                    {"x-filfli_MT_FILFLA.gb-18030", null, "x-filfli_MT_FILFLA"},
                    {
                        "no-no-ny.utf8@B", null, "no_NO_NY_B" /* not: "nn_NO" [alan ICU3.0] */
                    }, /* @ ignored unless variant is empty */

                    /* fleshing out canonicalization */
                    /* sort keywords, ';' is separator so not present at end in canonical form */
                    {
                        "en_Hant_IL_VALLEY_GIRL@currency=EUR;calendar=Japanese;",
                        "en_Hant_IL_VALLEY_GIRL@calendar=Japanese;currency=EUR",
                        "en_Hant_IL_VALLEY_GIRL@calendar=Japanese;currency=EUR"
                    },
                    /* already-canonical ids are not changed */
                    {
                        "en_Hant_IL_VALLEY_GIRL@calendar=Japanese;currency=EUR",
                        "en_Hant_IL_VALLEY_GIRL@calendar=Japanese;currency=EUR",
                        "en_Hant_IL_VALLEY_GIRL@calendar=Japanese;currency=EUR"
                    },
                    /* norwegian is just too weird, if we handle things in their full generality */
                    /* this is a negative test to show that we DO NOT handle 'lang=no,var=NY' specially. */
                    {
                        "no-Hant-GB_NY@currency=$$$",
                        "no_Hant_GB_NY@currency=$$$",
                        "no_Hant_GB_NY@currency=$$$" /* not: "nn_Hant_GB@currency=$$$" [alan ICU3.0] */
                    },

                    /* test cases reflecting internal resource bundle usage */
                    /* root is just a language */
                    {"root@kw=foo", "root@kw=foo", "root@kw=foo"},
                    /* level 2 canonicalization should not touch basename when there are keywords and it is null */
                    {"@calendar=gregorian", "@calendar=gregorian", "@calendar=gregorian"},

                    // Before ICU 64, ICU locale canonicalization had some additional mappings.
                    // They were removed for ICU-20187 "drop support for long-obsolete locale ID
                    // variants".
                    // The following now use standard canonicalization.
                    {"ca_ES_PREEURO", "ca_ES_PREEURO", "ca_ES_PREEURO"},
                    {"de_AT_PREEURO", "de_AT_PREEURO", "de_AT_PREEURO"},
                    {"de_DE_PREEURO", "de_DE_PREEURO", "de_DE_PREEURO"},
                    {"de_LU_PREEURO", "de_LU_PREEURO", "de_LU_PREEURO"},
                    {"el_GR_PREEURO", "el_GR_PREEURO", "el_GR_PREEURO"},
                    {"en_BE_PREEURO", "en_BE_PREEURO", "en_BE_PREEURO"},
                    {"en_IE_PREEURO", "en_IE_PREEURO", "en_IE_PREEURO"},
                    {"es_ES_PREEURO", "es_ES_PREEURO", "es_ES_PREEURO"},
                    {"eu_ES_PREEURO", "eu_ES_PREEURO", "eu_ES_PREEURO"},
                    {"fi_FI_PREEURO", "fi_FI_PREEURO", "fi_FI_PREEURO"},
                    {"fr_BE_PREEURO", "fr_BE_PREEURO", "fr_BE_PREEURO"},
                    {"fr_FR_PREEURO", "fr_FR_PREEURO", "fr_FR_PREEURO"},
                    {"fr_LU_PREEURO", "fr_LU_PREEURO", "fr_LU_PREEURO"},
                    {"ga_IE_PREEURO", "ga_IE_PREEURO", "ga_IE_PREEURO"},
                    {"gl_ES_PREEURO", "gl_ES_PREEURO", "gl_ES_PREEURO"},
                    {"it_IT_PREEURO", "it_IT_PREEURO", "it_IT_PREEURO"},
                    {"nl_BE_PREEURO", "nl_BE_PREEURO", "nl_BE_PREEURO"},
                    {"nl_NL_PREEURO", "nl_NL_PREEURO", "nl_NL_PREEURO"},
                    {"pt_PT_PREEURO", "pt_PT_PREEURO", "pt_PT_PREEURO"},
                    {"de__PHONEBOOK", "de__PHONEBOOK", "de__PHONEBOOK"},
                    {"de_PHONEBOOK", "de__PHONEBOOK", "de__PHONEBOOK"},
                    {"en_GB_EURO", "en_GB_EURO", "en_GB_EURO"},
                    {"en_GB@EURO", null, "en_GB_EURO"}, /* POSIX ID */
                    {"es__TRADITIONAL", "es__TRADITIONAL", "es__TRADITIONAL"},
                    {"hi__DIRECT", "hi__DIRECT", "hi__DIRECT"},
                    {"ja_JP_TRADITIONAL", "ja_JP_TRADITIONAL", "ja_JP_TRADITIONAL"},
                    {"th_TH_TRADITIONAL", "th_TH_TRADITIONAL", "th_TH_TRADITIONAL"},
                    {"zh_TW_STROKE", "zh_TW_STROKE", "zh_TW_STROKE"},
                    {"zh__PINYIN", "zh__PINYIN", "zh__PINYIN"},
                    {"qz-qz@Euro", null, "qz_QZ_EURO"}, /* qz-qz uses private use iso codes */
                    {"sr-SP-Cyrl", "sr_SP_CYRL", "sr_SP_CYRL"}, /* .NET name */
                    {"sr-SP-Latn", "sr_SP_LATN", "sr_SP_LATN"}, /* .NET name */
                    {"sr_YU_CYRILLIC", "sr_YU_CYRILLIC", "sr_RS_CYRILLIC"}, /* Linux name */
                    {"uz-UZ-Cyrl", "uz_UZ_CYRL", "uz_UZ_CYRL"}, /* .NET name */
                    {"uz-UZ-Latn", "uz_UZ_LATN", "uz_UZ_LATN"}, /* .NET name */
                    {"zh-CHS", "zh_CHS", "zh_CHS"}, /* .NET name */
                    {"zh-CHT", "zh_CHT", "zh_CHT"}, /* .NET name This may change back to zh_Hant */
                    /* PRE_EURO and EURO conversions don't affect other keywords */
                    /* not in spec
                    { "es_ES_PREEURO@CALendar=Japanese", "es_ES_PREEURO@calendar=Japanese", "es_ES@calendar=Japanese;currency=ESP" },
                    { "es_ES_EURO@SHOUT=zipeedeedoodah", "es_ES_EURO@shout=zipeedeedoodah", "es_ES@currency=EUR;shout=zipeedeedoodah" },
                      */
                    /* currency keyword overrides PRE_EURO and EURO currency */
                    /* not in spec
                    { "es_ES_PREEURO@currency=EUR", "es_ES_PREEURO@currency=EUR", "es_ES@currency=EUR" },
                    { "es_ES_EURO@currency=ESP", "es_ES_EURO@currency=ESP", "es_ES@currency=ESP" },
                      */
                };

        for (int i = 0; i < testCases.length; i++) {
            String[] testCase = testCases[i];
            String source = testCase[0];
            String level1Expected = testCase[1];
            String level2Expected = testCase[2];

            if (level1Expected
                    != null) { // null means we have no expectations for how this case is handled
                String level1 = ULocale.getName(source);
                if (!level1.equals(level1Expected)) {
                    errln(
                            "ULocale.getName error for: '"
                                    + source
                                    + "' expected: '"
                                    + level1Expected
                                    + "' but got: '"
                                    + level1
                                    + "'");
                } else {
                    logln("Ulocale.getName for: '" + source + "' returned: '" + level1 + "'");
                }
            } else {
                logln("ULocale.getName skipped: '" + source + "'");
            }

            if (level2Expected != null) {
                if (source.startsWith("zh_CN")
                        || source.startsWith("zh_TW")
                        || source.startsWith("uz-UZ")) {
                    continue;
                }
                String level2 = ULocale.canonicalize(source);
                if (!level2.equals(level2Expected)) {
                    errln(
                            "ULocale.getName error for: '"
                                    + source
                                    + "' expected: '"
                                    + level2Expected
                                    + "' but got: '"
                                    + level2
                                    + "'");
                } else {
                    logln("Ulocale.canonicalize for: '" + source + "' returned: '" + level2 + "'");
                }
            } else {
                logln("ULocale.canonicalize skipped: '" + source + "'");
            }
        }
    }

    @Test
    public void TestGetAvailable() {
        ULocale[] locales = ULocale.getAvailableLocales();
        if (locales.length < 1) {
            errln("Did not get any results from getAvailableLocales");
        }
        final Pattern PATTERN = Pattern.compile("^\\p{Lower}{2,3}$");
        for (ULocale locale : locales) {
            if (!PATTERN.matcher(locale.getLanguage()).matches()) {
                errln("Got impossible locale from getAvailableLocales: " + locale.getName());
            }
        }
    }

    @Test
    public void TestGetAvailableByType() {
        assertEquals(
                "countAvailable() should be same in old and new methods",
                ULocale.getAvailableLocales().length,
                ULocale.getAvailableLocalesByType(ULocale.AvailableType.DEFAULT).size());

        assertEquals(
                "getAvailable() should return same in old and new methods",
                Arrays.asList(ULocale.getAvailableLocales()),
                ULocale.getAvailableLocalesByType(ULocale.AvailableType.DEFAULT));

        Collection<ULocale> legacyLocales =
                ULocale.getAvailableLocalesByType(ULocale.AvailableType.ONLY_LEGACY_ALIASES);
        assertTrue("getAvailable() legacy/alias should return nonempty", legacyLocales.size() > 0);

        boolean found_he = false;
        boolean found_iw = false;
        for (ULocale loc : legacyLocales) {
            if (loc.getName().equals("he")) {
                found_he = true;
            }
            if (loc.getName().equals("iw")) {
                found_iw = true;
            }
        }
        assertFalse("Should NOT have found he amongst the legacy/alias locales", found_he);
        assertTrue("Should have found iw amongst the legacy/alias locales", found_iw);

        Collection<ULocale> allLocales =
                ULocale.getAvailableLocalesByType(ULocale.AvailableType.WITH_LEGACY_ALIASES);
        found_he = false;
        found_iw = false;
        for (ULocale loc : allLocales) {
            if (loc.getName().equals("he")) {
                found_he = true;
            }
            if (loc.getName().equals("iw")) {
                found_iw = true;
            }
        }
        assertTrue("Should have found he amongst the legacy/alias locales", found_he);
        assertTrue("Should have found iw amongst the legacy/alias locales", found_iw);
    }

    @Test
    public void TestDisplayNames() {
        // consistency check, also check that all data is available
        {
            ULocale[] locales = ULocale.getAvailableLocales();
            for (int i = 0; i < locales.length; ++i) {
                ULocale l = locales[i];
                String name = l.getDisplayName();

                logln(
                        l
                                + " --> "
                                + name
                                + ", "
                                + l.getDisplayName(ULocale.GERMAN)
                                + ", "
                                + l.getDisplayName(ULocale.FRANCE));

                String language = l.getDisplayLanguage();
                String script = l.getDisplayScriptInContext();
                String country = l.getDisplayCountry();
                String variant = l.getDisplayVariant();

                checkName(name, language, script, country, variant, ULocale.getDefault());

                for (int j = 0; j < locales.length; ++j) {
                    ULocale dl = locales[j];

                    name = l.getDisplayName(dl);
                    language = l.getDisplayLanguage(dl);
                    script = l.getDisplayScriptInContext(dl);
                    country = l.getDisplayCountry(dl);
                    variant = l.getDisplayVariant(dl);

                    if (!checkName(name, language, script, country, variant, dl)) {
                        break;
                    }
                }
            }
        }
        // spot check
        {
            ULocale[] locales = {ULocale.US, ULocale.GERMANY, ULocale.FRANCE};
            String[] names = {"Chinese (China)", "Chinesisch (China)", "chinois (Chine)"};
            String[] names2 = {
                "Simplified Chinese (China)",
                "Chinesisch (vereinfacht) (China)",
                "chinois simplifi\u00E9 (Chine)"
            };
            ULocale locale = new ULocale("zh_CN");
            ULocale locale2 = new ULocale("zh_Hans_CN");

            for (int i = 0; i < locales.length; ++i) {
                String name = locale.getDisplayName(locales[i]);
                if (!names[i].equals(name)) {
                    errln("expected '" + names[i] + "' but got '" + name + "'");
                }
            }
            for (int i = 0; i < locales.length; ++i) {
                String name = locale2.getDisplayNameWithDialect(locales[i]);
                if (!names2[i].equals(name)) {
                    errln("expected '" + names2[i] + "' but got '" + name + "'");
                }
            }
        }
        // test use of context
        {
            final DisplayContext NM_STD = DisplayContext.STANDARD_NAMES;
            final DisplayContext NM_DIA = DisplayContext.DIALECT_NAMES;
            final DisplayContext CAP_BEG = DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE;
            final DisplayContext CAP_MID = DisplayContext.CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE;
            final DisplayContext CAP_UIL = DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU;
            final DisplayContext CAP_STA = DisplayContext.CAPITALIZATION_FOR_STANDALONE;
            final DisplayContext CAP_NON = DisplayContext.CAPITALIZATION_NONE;
            final DisplayContext LEN_FU = DisplayContext.LENGTH_FULL;
            final DisplayContext LEN_SH = DisplayContext.LENGTH_SHORT;
            final DisplayContext SUB_SU = DisplayContext.SUBSTITUTE;
            final DisplayContext SUB_NO = DisplayContext.NO_SUBSTITUTE;

            class Item {
                public String displayLocale;
                public DisplayContext dialectHandling;
                public DisplayContext capitalization;
                public DisplayContext nameLength;
                public DisplayContext substituteHandling;
                public String localeToBeNamed;
                public String result;

                public Item(
                        String dLoc,
                        DisplayContext dia,
                        DisplayContext cap,
                        DisplayContext nameLen,
                        DisplayContext sub,
                        String locToName,
                        String res) {
                    displayLocale = dLoc;
                    dialectHandling = dia;
                    capitalization = cap;
                    nameLength = nameLen;
                    substituteHandling = sub;
                    localeToBeNamed = locToName;
                    result = res;
                }
            }
            ;
            final Item[] items = {
                new Item("da", NM_STD, CAP_MID, LEN_FU, SUB_SU, "en", "engelsk"),
                new Item("da", NM_STD, CAP_BEG, LEN_FU, SUB_SU, "en", "Engelsk"),
                new Item("da", NM_STD, CAP_UIL, LEN_FU, SUB_SU, "en", "Engelsk"),
                new Item("da", NM_STD, CAP_STA, LEN_FU, SUB_SU, "en", "engelsk"),
                new Item(
                        "da",
                        NM_STD,
                        CAP_MID,
                        LEN_FU,
                        SUB_SU,
                        "en@calendar=buddhist",
                        "engelsk (buddhistisk kalender)"),
                new Item(
                        "da",
                        NM_STD,
                        CAP_BEG,
                        LEN_FU,
                        SUB_SU,
                        "en@calendar=buddhist",
                        "Engelsk (buddhistisk kalender)"),
                new Item(
                        "da",
                        NM_STD,
                        CAP_UIL,
                        LEN_FU,
                        SUB_SU,
                        "en@calendar=buddhist",
                        "Engelsk (buddhistisk kalender)"),
                new Item(
                        "da",
                        NM_STD,
                        CAP_STA,
                        LEN_FU,
                        SUB_SU,
                        "en@calendar=buddhist",
                        "engelsk (buddhistisk kalender)"),
                new Item(
                        "da", NM_STD, CAP_MID, LEN_FU, SUB_SU, "en_GB", "engelsk (Storbritannien)"),
                new Item(
                        "da", NM_STD, CAP_BEG, LEN_FU, SUB_SU, "en_GB", "Engelsk (Storbritannien)"),
                new Item(
                        "da", NM_STD, CAP_UIL, LEN_FU, SUB_SU, "en_GB", "Engelsk (Storbritannien)"),
                new Item(
                        "da", NM_STD, CAP_STA, LEN_FU, SUB_SU, "en_GB", "engelsk (Storbritannien)"),
                new Item(
                        "da", NM_STD, CAP_MID, LEN_SH, SUB_SU, "en_GB", "engelsk (Storbritannien)"),
                new Item(
                        "da", NM_STD, CAP_BEG, LEN_SH, SUB_SU, "en_GB", "Engelsk (Storbritannien)"),
                new Item(
                        "da", NM_STD, CAP_UIL, LEN_SH, SUB_SU, "en_GB", "Engelsk (Storbritannien)"),
                new Item(
                        "da", NM_STD, CAP_STA, LEN_SH, SUB_SU, "en_GB", "engelsk (Storbritannien)"),
                new Item("da", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "en_GB", "britisk engelsk"),
                new Item("da", NM_DIA, CAP_BEG, LEN_FU, SUB_SU, "en_GB", "Britisk engelsk"),
                new Item("da", NM_DIA, CAP_UIL, LEN_FU, SUB_SU, "en_GB", "Britisk engelsk"),
                new Item("da", NM_DIA, CAP_STA, LEN_FU, SUB_SU, "en_GB", "britisk engelsk"),
                new Item("es", NM_STD, CAP_MID, LEN_FU, SUB_SU, "en", "inglés"),
                new Item("es", NM_STD, CAP_BEG, LEN_FU, SUB_SU, "en", "Inglés"),
                new Item("es", NM_STD, CAP_UIL, LEN_FU, SUB_SU, "en", "Inglés"),
                new Item("es", NM_STD, CAP_STA, LEN_FU, SUB_SU, "en", "Inglés"),
                new Item("es", NM_STD, CAP_MID, LEN_FU, SUB_SU, "en_GB", "inglés (Reino Unido)"),
                new Item("es", NM_STD, CAP_BEG, LEN_FU, SUB_SU, "en_GB", "Inglés (Reino Unido)"),
                new Item("es", NM_STD, CAP_UIL, LEN_FU, SUB_SU, "en_GB", "Inglés (Reino Unido)"),
                new Item("es", NM_STD, CAP_STA, LEN_FU, SUB_SU, "en_GB", "Inglés (Reino Unido)"),
                new Item("es", NM_STD, CAP_MID, LEN_SH, SUB_SU, "en_GB", "inglés (RU)"),
                new Item("es", NM_STD, CAP_BEG, LEN_SH, SUB_SU, "en_GB", "Inglés (RU)"),
                new Item("es", NM_STD, CAP_UIL, LEN_SH, SUB_SU, "en_GB", "Inglés (RU)"),
                new Item("es", NM_STD, CAP_STA, LEN_SH, SUB_SU, "en_GB", "Inglés (RU)"),
                new Item("es", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "en_GB", "inglés británico"),
                new Item("es", NM_DIA, CAP_BEG, LEN_FU, SUB_SU, "en_GB", "Inglés británico"),
                new Item("es", NM_DIA, CAP_UIL, LEN_FU, SUB_SU, "en_GB", "Inglés británico"),
                new Item("es", NM_DIA, CAP_STA, LEN_FU, SUB_SU, "en_GB", "Inglés británico"),
                new Item(
                        "ru",
                        NM_STD,
                        CAP_MID,
                        LEN_FU,
                        SUB_SU,
                        "uz_Latn",
                        "\u0443\u0437\u0431\u0435\u043A\u0441\u043A\u0438\u0439 (\u043B\u0430\u0442\u0438\u043D\u0438\u0446\u0430)"),
                new Item(
                        "ru",
                        NM_STD,
                        CAP_BEG,
                        LEN_FU,
                        SUB_SU,
                        "uz_Latn",
                        "\u0423\u0437\u0431\u0435\u043A\u0441\u043A\u0438\u0439 (\u043B\u0430\u0442\u0438\u043D\u0438\u0446\u0430)"),
                new Item(
                        "ru",
                        NM_STD,
                        CAP_UIL,
                        LEN_FU,
                        SUB_SU,
                        "uz_Latn",
                        "\u0423\u0437\u0431\u0435\u043A\u0441\u043A\u0438\u0439 (\u043B\u0430\u0442\u0438\u043D\u0438\u0446\u0430)"),
                new Item(
                        "ru",
                        NM_STD,
                        CAP_STA,
                        LEN_FU,
                        SUB_SU,
                        "uz_Latn",
                        "\u0423\u0437\u0431\u0435\u043A\u0441\u043A\u0438\u0439 (\u043B\u0430\u0442\u0438\u043D\u0438\u0446\u0430)"),
                new Item(
                        "en",
                        NM_STD,
                        CAP_MID,
                        LEN_FU,
                        SUB_SU,
                        "ur@numbers=latn",
                        "Urdu (Western Digits)"),
                new Item(
                        "en",
                        NM_STD,
                        CAP_MID,
                        LEN_FU,
                        SUB_SU,
                        "ur@numbers=arabext",
                        "Urdu (Extended Arabic-Indic Digits)"),
                new Item(
                        "en",
                        NM_STD,
                        CAP_MID,
                        LEN_SH,
                        SUB_SU,
                        "ur@numbers=arabext",
                        "Urdu (X Arabic-Indic Digits)"),
                new Item("af", NM_STD, CAP_NON, LEN_FU, SUB_NO, "aa", "Afar"),
                // https://unicode-org.atlassian.net/browse/ICU-20870:
                new Item("en", NM_STD, CAP_MID, LEN_FU, SUB_SU, "fa_AF", "Persian (Afghanistan)"),
                new Item("en", NM_STD, CAP_MID, LEN_FU, SUB_SU, "prs", "Dari"),
                new Item("en", NM_STD, CAP_MID, LEN_FU, SUB_SU, "prs_AF", "Dari (Afghanistan)"),
                new Item("en", NM_STD, CAP_MID, LEN_FU, SUB_SU, "prs_TJ", "Dari (Tajikistan)"),
                new Item("en", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "fa_AF", "Dari"),
                new Item("en", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "prs", "Dari"),
                new Item("en", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "prs_AF", "Dari (Afghanistan)"),
                new Item("en", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "prs_TJ", "Dari (Tajikistan)"),
                // https://unicode-org.atlassian.net/browse/ICU-21742:
                new Item("en", NM_STD, CAP_MID, LEN_FU, SUB_SU, "ji", "Yiddish"),
                new Item("en", NM_STD, CAP_MID, LEN_FU, SUB_SU, "ji_US", "Yiddish (United States)"),
                new Item("yi", NM_STD, CAP_MID, LEN_FU, SUB_SU, "ji", "ייִדיש"),
                new Item(
                        "yi",
                        NM_STD,
                        CAP_MID,
                        LEN_FU,
                        SUB_SU,
                        "ji_US",
                        "ייִדיש (פֿאַראייניגטע שטאַטן)"),
                // https://unicode-org.atlassian.net/browse/ICU-11563:
                new Item("en", NM_STD, CAP_MID, LEN_FU, SUB_SU, "mo", "Romanian"),
                new Item("en", NM_STD, CAP_MID, LEN_FU, SUB_SU, "mo_MD", "Romanian (Moldova)"),
                new Item("en", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "mo", "Romanian"),
                new Item("ro", NM_STD, CAP_MID, LEN_FU, SUB_SU, "mo", "română"),
                new Item(
                        "ro",
                        NM_STD,
                        CAP_MID,
                        LEN_FU,
                        SUB_SU,
                        "mo_MD",
                        "română (Republica Moldova)"),
                // https://unicode-org.atlassian.net/browse/ICU-10297:
                new Item("zh", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "ar_001", "现代标准阿拉伯语"),
                new Item("zh", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "nl_BE", "弗拉芒语"),
                new Item("zh", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "ro_MD", "摩尔多瓦语"),
                new Item("zh", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "en_AU", "澳大利亚英语"),
                new Item("zh", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "en_CA", "加拿大英语"),
                new Item("zh", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "en_GB", "英国英语"),
                new Item("zh", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "en_US", "美国英语"),
                new Item("zh", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "es_419", "拉丁美洲西班牙语"),
                new Item("zh", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "es_ES", "欧洲西班牙语"),
                new Item("zh", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "es_MX", "墨西哥西班牙语"),
                new Item("zh", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "fr_CA", "加拿大法语"),
                new Item("zh", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "fr_CH", "瑞士法语"),
                new Item("zh_Hant", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "ar_001", "現代標準阿拉伯文"),
                new Item("zh_Hant", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "nl_BE", "法蘭德斯文"),
                new Item("zh_Hant", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "ro_MD", "摩爾多瓦文"),
                new Item("zh_Hant", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "en_AU", "英文（澳洲）"),
                new Item("zh_Hant", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "en_CA", "英文（加拿大）"),
                new Item("zh_Hant", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "en_GB", "英文（英國）"),
                new Item("zh_Hant", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "en_US", "英文（美國）"),
                new Item("zh_Hant", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "es_419", "西班牙文（拉丁美洲）"),
                new Item("zh_Hant", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "es_ES", "西班牙文（西班牙）"),
                new Item("zh_Hant", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "es_MX", "西班牙文（墨西哥）"),
                new Item("zh_Hant", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "fr_CA", "法文（加拿大）"),
                new Item("zh_Hant", NM_DIA, CAP_MID, LEN_FU, SUB_SU, "fr_CH", "法文（瑞士）"),
            };
            for (Item item : items) {
                ULocale locale = new ULocale(item.displayLocale);
                LocaleDisplayNames ldn =
                        LocaleDisplayNames.getInstance(
                                locale,
                                item.dialectHandling,
                                item.capitalization,
                                item.nameLength,
                                item.substituteHandling);
                DisplayContext dialectHandling =
                        ldn.getContext(DisplayContext.Type.DIALECT_HANDLING);
                assertEquals(
                        "consistent dialect handling",
                        dialectHandling == DisplayContext.DIALECT_NAMES,
                        ldn.getDialectHandling()
                                == LocaleDisplayNames.DialectHandling.DIALECT_NAMES);
                DisplayContext capitalization = ldn.getContext(DisplayContext.Type.CAPITALIZATION);
                DisplayContext nameLength = ldn.getContext(DisplayContext.Type.DISPLAY_LENGTH);
                DisplayContext substituteHandling =
                        ldn.getContext(DisplayContext.Type.SUBSTITUTE_HANDLING);
                if (dialectHandling != item.dialectHandling
                        || capitalization != item.capitalization
                        || nameLength != item.nameLength
                        || substituteHandling != item.substituteHandling) {
                    errln(
                            "FAIL: displayLoc: "
                                    + item.displayLocale
                                    + ", dialectNam?: "
                                    + item.dialectHandling
                                    + ", capitalize: "
                                    + item.capitalization
                                    + ", nameLen: "
                                    + item.nameLength
                                    + ", substituteHandling: "
                                    + item.substituteHandling
                                    + ", locToName: "
                                    + item.localeToBeNamed
                                    + ", => read back dialectNam?: "
                                    + dialectHandling
                                    + ", capitalize: "
                                    + capitalization
                                    + ", nameLen: "
                                    + nameLength
                                    + ", substituteHandling: "
                                    + substituteHandling);
                } else {
                    boolean checkULocaleDisplay =
                            (dialectHandling == NM_STD
                                    && capitalization == CAP_MID
                                    && nameLength == LEN_FU
                                    && substituteHandling == SUB_SU);
                    boolean checkJustLanguage = item.localeToBeNamed.matches("[a-z]{2,3}");
                    String result = ldn.localeDisplayName(item.localeToBeNamed);
                    if (!(item.result == null && result == null)
                            && !(result != null && result.equals(item.result))) {
                        errln(
                                "FAIL: displayLoc: "
                                        + item.displayLocale
                                        + ", dialectNam?: "
                                        + item.dialectHandling
                                        + ", capitalize: "
                                        + item.capitalization
                                        + ", nameLen: "
                                        + item.nameLength
                                        + ", substituteHandling: "
                                        + item.substituteHandling
                                        + "; locToName: "
                                        + item.localeToBeNamed
                                        + ", => expected result: "
                                        + item.result
                                        + ", got LDN name: "
                                        + result);
                    }
                    if (checkULocaleDisplay) {
                        result = ULocale.getDisplayName(item.localeToBeNamed, locale);
                        if (!(item.result == null && result == null)
                                && !(result != null && result.equals(item.result))) {
                            errln(
                                    "FAIL: displayLoc: "
                                            + item.displayLocale
                                            + ", dialectNam: std, capitalize: mid, nameLen: full, substitute: yes; locToName: "
                                            + item.localeToBeNamed
                                            + ", => expected result: "
                                            + item.result
                                            + ", got ULoc name: "
                                            + result);
                        }
                    }
                    if (checkJustLanguage) {
                        result = ldn.languageDisplayName(item.localeToBeNamed);
                        if (!(item.result == null && result == null)
                                && !(result != null && result.equals(item.result))) {
                            errln(
                                    "FAIL: displayLoc: "
                                            + item.displayLocale
                                            + ", dialectNam?: "
                                            + item.dialectHandling
                                            + ", capitalize: "
                                            + item.capitalization
                                            + ", nameLen: "
                                            + item.nameLength
                                            + ", substituteHandling: "
                                            + item.substituteHandling
                                            + "; locToName: "
                                            + item.localeToBeNamed
                                            + ", => expected result: "
                                            + item.result
                                            + ", got LDN lang: "
                                            + result);
                        }
                        if (checkULocaleDisplay) {
                            result = ULocale.getDisplayLanguage(item.localeToBeNamed, locale);
                            if (!(item.result == null && result == null)
                                    && !(result != null && result.equals(item.result))) {
                                errln(
                                        "FAIL: displayLoc: "
                                                + item.displayLocale
                                                + ", dialectNam: std, capitalize: mid, nameLen: full, substitute: yes; locToName: "
                                                + item.localeToBeNamed
                                                + ", => expected result: "
                                                + item.result
                                                + ", got ULoc lang: "
                                                + result);
                            }
                        }
                    }
                }
            }
        }
    }

    @Test
    public void TestDisplayLanguageWithDialectCoverage() {
        // Coverage test. Implementation is in class LocaleDisplayNames.
        assertFalse(
                "en in system default locale: anything but empty",
                ULocale.ENGLISH.getDisplayLanguageWithDialect().isEmpty());
        assertEquals(
                "en in de",
                "Englisch",
                ULocale.ENGLISH.getDisplayLanguageWithDialect(ULocale.GERMAN));
        assertEquals(
                "en (string) in de",
                "Englisch",
                ULocale.getDisplayLanguageWithDialect("en", ULocale.GERMAN));
        assertEquals(
                "en (string) in de (string)",
                "Englisch",
                ULocale.getDisplayLanguageWithDialect("en", "de"));
    }

    @Test
    public void TestDisplayNameWithDialectCoverage() {
        // Coverage test. Implementation is in class LocaleDisplayNames.
        // In CLDR 63, de removed the dialect names, use fr instead
        assertFalse(
                "en-GB in system default locale: anything but empty",
                ULocale.UK.getDisplayNameWithDialect().isEmpty());
        assertEquals(
                "en-GB in fr",
                "anglais britannique",
                ULocale.UK.getDisplayNameWithDialect(ULocale.FRENCH));
        assertEquals(
                "en-GB (string) in fr",
                "anglais britannique",
                ULocale.getDisplayNameWithDialect("en-GB", ULocale.FRENCH));
        assertEquals(
                "en-GB (string) in fr (string)",
                "anglais britannique",
                ULocale.getDisplayNameWithDialect("en-GB", "fr"));
    }

    @Test
    public void TestDisplayScriptCoverage() {
        // Coverage test. Implementation is in class LocaleDisplayNames.
        assertFalse(
                "zh-Hans in system default locale: anything but empty",
                ULocale.SIMPLIFIED_CHINESE.getDisplayScript().isEmpty());
        // Stand-alone script name, so not just "Vereinfacht".
        assertEquals(
                "zh-Hans in de",
                "Vereinfachtes Chinesisch",
                ULocale.SIMPLIFIED_CHINESE.getDisplayScript(ULocale.GERMAN));
        assertEquals(
                "zh-Hans (string) in de",
                "Vereinfachtes Chinesisch",
                ULocale.getDisplayScript("zh-Hans", ULocale.GERMAN));
        assertEquals(
                "zh-Hans (string) in de (string)",
                "Vereinfachtes Chinesisch",
                ULocale.getDisplayScript("zh-Hans", "de"));
    }

    private boolean checkName(
            String name,
            String language,
            String script,
            String country,
            String variant,
            ULocale dl) {
        if (!checkInclusion(dl, name, language, "language")) {
            return false;
        }
        if (!checkInclusion(dl, name, script, "script")) {
            return false;
        }
        if (!checkInclusion(dl, name, country, "country")) {
            return false;
        }
        if (!checkInclusion(dl, name, variant, "variant")) {
            return false;
        }
        return true;
    }

    private boolean checkInclusion(
            ULocale dl, String name, String substring, String substringName) {
        if (substring.length() > 0 && name.indexOf(substring) == -1) {
            String country2 =
                    substring
                            .replace('(', '[')
                            .replace(')', ']')
                            .replace('（', '［')
                            .replace('）', '］');
            if (name.indexOf(country2) == -1) {
                errln(
                        "loc: "
                                + dl
                                + " name '"
                                + name
                                + "' does not contain "
                                + substringName
                                + " '"
                                + substring
                                + "'");
                return false;
            }
        }
        return true;
    }

    @Test
    public void TestCoverage() {
        {
            // Cover displayXXX
            int i, j;
            String localeID = "zh_CN";
            String name, language, script, country, variant;
            logln("Covering APIs with signature displayXXX(String, String)");
            for (i = 0; i < LOCALE_SIZE; i++) {
                // localeID String
                String testLocale = (rawData2[NAME][i]);

                logln("Testing " + testLocale + ".....");
                name = ULocale.getDisplayName(localeID, testLocale);
                language = ULocale.getDisplayLanguage(localeID, testLocale);
                script = ULocale.getDisplayScriptInContext(localeID, testLocale);
                country = ULocale.getDisplayCountry(localeID, testLocale);
                variant = ULocale.getDisplayVariant(localeID, testLocale);
                if (!checkName(name, language, script, country, variant, new ULocale(testLocale))) {
                    break;
                }
            }

            logln("Covering APIs with signature displayXXX(String, ULocale)\n");
            for (j = 0; j < LOCALE_SIZE; j++) {
                String testLocale = (rawData2[NAME][j]);
                ULocale loc = new ULocale(testLocale);

                logln("Testing " + testLocale + ".....");
                name = ULocale.getDisplayName(localeID, loc);
                language = ULocale.getDisplayLanguage(localeID, loc);
                script = ULocale.getDisplayScriptInContext(localeID, loc);
                country = ULocale.getDisplayCountry(localeID, loc);
                variant = ULocale.getDisplayVariant(localeID, loc);

                if (!checkName(name, language, script, country, variant, loc)) {
                    break;
                }
            }
        }
        ULocale loc1 = new ULocale("en_US_BROOKLYN");
        ULocale loc2 = new ULocale("en", "US", "BROOKLYN");
        if (!loc2.equals(loc1)) {
            errln("ULocale.ULocale(String a, String b, String c)");
        }

        ULocale loc3 = new ULocale("en_US");
        ULocale loc4 = new ULocale("en", "US");
        if (!loc4.equals(loc3)) {
            errln("ULocale.ULocale(String a, String b)");
        }

        ULocale loc5 = loc4.clone();
        if (!loc5.equals(loc4)) {
            errln("ULocale.clone should get the same ULocale");
        }
        ULocale.getISOCountries(); // To check the result ?!
    }

    @Test
    public void TestBamBm() {
        // "bam" shouldn't be there since the official code is 'bm'
        String[] isoLanguages = ULocale.getISOLanguages();
        for (int i = 0; i < isoLanguages.length; ++i) {
            if ("bam".equals(isoLanguages[i])) {
                errln("found bam");
            }
            if (i > 0 && isoLanguages[i].compareTo(isoLanguages[i - 1]) <= 0) {
                errln(
                        "language list out of order: '"
                                + isoLanguages[i]
                                + " <= "
                                + isoLanguages[i - 1]);
            }
        }
    }

    @Test
    public void TestDisplayKeyword() {
        // prepare testing data
        initHashtable();
        String[] data = {
            "en_US@collation=phonebook;calendar=islamic-civil",
            "zh_Hans@collation=pinyin;calendar=chinese",
            "foo_Bar_BAZ@collation=traditional;calendar=buddhist"
        };

        for (int i = 0; i < data.length; i++) {
            String localeID = data[i];
            logln("");
            logln("Testing locale " + localeID + " ...");
            ULocale loc = new ULocale(localeID);

            Iterator<String> it = loc.getKeywords();
            Iterator<String> it2 = ULocale.getKeywords(localeID);
            // it and it2 are not equal here. No way to verify their equivalence yet.
            while (it.hasNext()) {
                String key = it.next();
                String key2 = it2.next();
                if (!key.equals(key2)) {
                    errln("FAIL: static and non-static getKeywords returned different results.");
                }

                // To verify display of Keyword
                // display the above key in English
                String s0 = ULocale.getDisplayKeyword(key); // display in default locale
                String s1 = ULocale.getDisplayKeyword(key, ULocale.US);
                String s2 = ULocale.getDisplayKeyword(key, "en_US");
                if (!s1.equals(s2)) {
                    errln("FAIL: one of the getDisplayKeyword methods failed.");
                }
                if (ULocale.getDefault().equals(ULocale.US) && !s1.equals(s0)) {
                    errln("FAIL: getDisplayKeyword methods failed for the default locale.");
                }
                if (!s1.equals(h[0].get(key))) {
                    errln(
                            "Locale "
                                    + localeID
                                    + " getDisplayKeyword for key: "
                                    + key
                                    + " in English expected \""
                                    + h[0].get(key)
                                    + "\" saw \""
                                    + s1
                                    + "\" instead");
                } else {
                    logln("OK: getDisplayKeyword for key: " + key + " in English got " + s1);
                }

                // display the key in S-Chinese
                s1 = ULocale.getDisplayKeyword(key, ULocale.CHINA);
                s2 = ULocale.getDisplayKeyword(key, "zh_Hans");
                if (!s1.equals(s2)) {
                    errln("one of the getDisplayKeyword methods failed.");
                }
                if (!s1.equals(h[1].get(key))) {
                    errln(
                            "Locale "
                                    + localeID
                                    + " getDisplayKeyword for key: "
                                    + key
                                    + " in Chinese expected \""
                                    + h[1].get(key)
                                    + "\" saw \""
                                    + s1
                                    + "\" instead");
                } else {
                    logln("OK: getDisplayKeyword for key: " + key + " in Chinese got " + s1);
                }

                // To verify display of Keyword values
                String type = loc.getKeywordValue(key);
                // display type in English
                String ss0 = loc.getDisplayKeywordValue(key);
                String ss1 = loc.getDisplayKeywordValue(key, ULocale.US);
                String ss2 = ULocale.getDisplayKeywordValue(localeID, key, "en_US");
                String ss3 = ULocale.getDisplayKeywordValue(localeID, key, ULocale.US);
                if (!ss1.equals(ss2) || !ss1.equals(ss3)) {
                    errln("FAIL: one of the getDisplayKeywordValue methods failed.");
                }
                if (ULocale.getDefault().equals(ULocale.US) && !ss1.equals(ss0)) {
                    errln("FAIL: getDisplayKeyword methods failed for the default locale.");
                }
                if (!ss1.equals(h[0].get(type))) {
                    errln(
                            " Locale "
                                    + localeID
                                    + " getDisplayKeywordValue for key: "
                                    + key
                                    + " in English expected \""
                                    + h[0].get(type)
                                    + "\" saw \""
                                    + ss1
                                    + "\" instead");
                } else {
                    logln("OK: getDisplayKeywordValue for key: " + key + " in English got " + ss1);
                }

                // display type in Chinese
                ss0 = loc.getDisplayKeywordValue(key);
                ss1 = loc.getDisplayKeywordValue(key, ULocale.CHINA);
                ss2 = ULocale.getDisplayKeywordValue(localeID, key, "zh_Hans");
                ss3 = ULocale.getDisplayKeywordValue(localeID, key, ULocale.CHINA);
                if (!ss1.equals(ss2) || !ss1.equals(ss3)) {
                    errln("one of the getDisplayKeywordValue methods failed.");
                }
                if (!ss1.equals(h[1].get(type))) {
                    errln(
                            "Locale "
                                    + localeID
                                    + " getDisplayKeywordValue for key: "
                                    + key
                                    + " in Chinese expected \""
                                    + h[1].get(type)
                                    + "\" saw \""
                                    + ss1
                                    + "\" instead");
                } else {
                    logln("OK: getDisplayKeywordValue for key: " + key + " in Chinese got " + ss1);
                }
            }
        }
    }

    @Test
    public void TestDisplayWithKeyword() {
        // Note, this test depends on locale display data for the U.S. and Taiwan.
        // If the data changes (in particular, the keyTypePattern may change for Taiwan),
        // this test will break.
        LocaleDisplayNames dn =
                LocaleDisplayNames.getInstance(ULocale.US, DialectHandling.DIALECT_NAMES);
        LocaleDisplayNames tdn =
                LocaleDisplayNames.getInstance(ULocale.TAIWAN, DialectHandling.DIALECT_NAMES);
        String name = dn.localeDisplayName("de@collation=phonebook");
        String target = "German (Phonebook Sort Order)";
        assertEquals("collation", target, name);

        name = tdn.localeDisplayName("de@collation=phonebook");
        target = "德文（電話簿排序）"; // \u5FB7\u6587\uFF08\u96FB\u8A71\u7C3F\u6392\u5E8F\uFF09
        assertEquals("collation", target, name);

        name = dn.localeDisplayName("ja@currency=JPY");
        target = "Japanese (Japanese Yen)";
        assertEquals("currency (JPY)", target, name);

        name = tdn.localeDisplayName("ja@currency=JPY");
        target = "日文（日圓）"; // \u65E5\u6587\uFF08\u65E5\u5713\uFF09
        assertEquals("currency (JPY)", target, name);

        name = dn.localeDisplayName("de@currency=XYZ");
        target = "German (Currency: XYZ)";
        assertEquals("currency (XYZ)", target, name);

        name = dn.localeDisplayName("de@collation=phonebook;currency=XYZ");
        target = "German (Phonebook Sort Order, Currency: XYZ)";
        assertEquals("currency", target, name);

        name = dn.localeDisplayName("de_Latn_DE");
        target = "German (Latin, Germany)";
        assertEquals("currency", target, name);

        name = tdn.localeDisplayName("de@currency=XYZ");
        target = "德文（貨幣：XYZ）"; // \u5FB7\u6587\uFF08\u8CA8\u5E63: XYZ\uFF09
        assertEquals("currency", target, name);

        name = tdn.localeDisplayName("de@collation=phonebook;currency=XYZ");
        target =
                "德文（電話簿排序，貨幣：XYZ）"; // \u5FB7\u6587\uFF08\u96FB\u8A71\u7C3F\u6392\u5E8F\uFF09，\u5FB7\u6587\uFF08\u8CA8\u5E63: XYZ\uFF09
        assertEquals("collation", target, name);

        name = dn.localeDisplayName("de@foo=bar");
        target = "German (foo=bar)";
        assertEquals("foo", target, name);

        name = tdn.localeDisplayName("de@foo=bar");
        target = "德文（foo=bar）"; // \u5FB7\u6587\uFF08foo=bar\uFF09
        assertEquals("foo", target, name);

        ULocale locale = ULocale.forLanguageTag("de-x-foobar");
        name = dn.localeDisplayName(locale);
        target = "German (Private-Use: foobar)";
        assertEquals("foobar", target, name);

        name = tdn.localeDisplayName(locale);
        target = "德文（私人使用：foobar）"; // \u5FB7\u6587\uFF08\u79C1\u4EBA\u4F7F\u7528: foobar\uFF09
        assertEquals("foobar", target, name);
    }

    @Test
    public void TestBug20407iVariantPreferredValue() {
        ULocale uloc = ULocale.createCanonical("hy-arevela");
        assertEquals("getName()", "hy", uloc.getName());
        uloc = ULocale.createCanonical("hy-arevmda");
        assertEquals("getName()", "hyw", uloc.getName());
    }

    private void initHashtable() {
        h[0] = new HashMap<String, String>();
        h[1] = new HashMap<String, String>();

        // display in English
        h[0].put("collation", "Sort Order");
        h[0].put("calendar", "Calendar");
        h[0].put("currency", "Currency");
        h[0].put("phonebook", "Phonebook Sort Order");
        h[0].put("pinyin", "Pinyin Sort Order");
        h[0].put("traditional", "Traditional Sort Order");
        h[0].put("stroke", "Stroke Order");
        h[0].put("japanese", "Japanese Calendar");
        h[0].put("buddhist", "Buddhist Calendar");
        h[0].put("islamic", "Hijri Calendar");
        h[0].put("islamic-civil", "Hijri Calendar (tabular, civil epoch)");
        h[0].put("hebrew", "Hebrew Calendar");
        h[0].put("chinese", "Chinese Calendar");
        h[0].put("gregorian", "Gregorian Calendar");

        // display in S-Chinese
        h[1].put("collation", "\u6392\u5E8F");
        h[1].put("calendar", "\u65E5\u5386");
        h[1].put("currency", "\u8D27\u5E01");
        h[1].put("phonebook", "\u7535\u8BDD\u7C3F\u6392\u5E8F");
        h[1].put("pinyin", "\u62FC\u97F3\u6392\u5E8F");
        h[1].put("stroke", "\u7B14\u5212\u987A\u5E8F");
        h[1].put("traditional", "\u4F20\u7EDF\u6392\u5E8F");
        h[1].put("japanese", "\u65E5\u672C\u65E5\u5386");
        h[1].put("buddhist", "\u4F5B\u5386");
        h[1].put("islamic", "伊斯兰历");
        h[1].put("islamic-civil", "表格式伊斯兰历（民用纪元）");
        h[1].put("hebrew", "\u5E0C\u4F2F\u6765\u65E5\u5386");
        h[1].put("chinese", "\u519C\u5386");
        h[1].put("gregorian", "\u516C\u5386");
    }

    // Hashtables for storing expected display of keys/types of locale in English and Chinese
    @SuppressWarnings("unchecked")
    private static Map<String, String>[] h = new Map[2];

    private static final String ACCEPT_LANGUAGE_TESTS[][] = {
        /*#      result  fallback? */
        /*0*/ {"mt_MT", "false"},
        /*1*/ {"en", "false"},
        /*2*/ {"en", "true"}, // fell back from en-zzz to en
        /*3*/ {null, "true"},
        /*4*/ {"es", "false"},
        /*5*/ {"de", "false"},
        /*6*/ {"zh_Hant_TW", "true"},
        /*7*/ {"zh_Hant", "true"},
    };

    private static final String ACCEPT_LANGUAGE_HTTP[] = {
        /*0*/ "mt-mt, ja;q=0.76, en-us;q=0.95, en;q=0.92, en-gb;q=0.89, fr;q=0.87, iu-ca;q=0.84, iu;q=0.82, ja-jp;q=0.79, mt;q=0.97, de-de;q=0.74, de;q=0.71, es;q=0.68, it-it;q=0.66, it;q=0.63, vi-vn;q=0.61, vi;q=0.58, nl-nl;q=0.55, nl;q=0.53, th-th-traditional;q=.01",
        /*1*/ "ja;q=0.5, en;q=0.8, tlh",
        /*2*/ "en-zzz, de-lx;q=0.8",
        /*3*/ "mga-ie;q=0.9, sux",
        /*4*/ "xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "
                + "xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "
                + "xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "
                + "xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "
                + "xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "
                + "xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "
                + "xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "
                + "xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "
                + "xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "
                + "xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "
                + "es",
        /*5*/ "de;q=.9, fr;q=.9, xxx-yyy, sr;q=.8",
        /*6*/ "zh-tw",
        /*7*/ "zh-hant-cn",
    };

    @Test
    public void TestAcceptLanguage() {
        for (int i = 0; i < (ACCEPT_LANGUAGE_HTTP.length); i++) {
            Boolean expectBoolean = Boolean.valueOf(ACCEPT_LANGUAGE_TESTS[i][1]);
            String expectLocale = ACCEPT_LANGUAGE_TESTS[i][0];

            logln("#" + i + ": expecting: " + expectLocale + " (" + expectBoolean + ")");

            boolean r[] = {false};
            ULocale n = ULocale.acceptLanguage(ACCEPT_LANGUAGE_HTTP[i], r);
            if ((n == null) && (expectLocale != null)) {
                errln("#" + i + ": result was null!");
                continue;
            }
            if (((n == null) && (expectLocale == null)) || (n.toString().equals(expectLocale))) {
                logln("#" + i + ": locale: OK.");
            } else {
                errln("#" + i + ": locale: expected " + expectLocale + " but got " + n);
            }
            Boolean actualBoolean = r[0];
            if (expectBoolean.equals(actualBoolean)) {
                logln("#" + i + ": fallback: OK.");
            } else {
                errln("#" + i + ": fallback: was " + actualBoolean + " expected " + expectBoolean);
            }
        }
    }

    @Test
    public void TestAcceptLanguage2() {
        for (int i = 0; i < (ACCEPT_LANGUAGE_HTTP.length); i++) {
            Boolean expectBoolean = Boolean.valueOf(ACCEPT_LANGUAGE_TESTS[i][1]);
            String expectLocale = ACCEPT_LANGUAGE_TESTS[i][0];

            logln("#" + i + ": expecting: " + expectLocale + " (" + expectBoolean + ")");

            boolean r[] = {false};
            Set<ULocale> desiredSet =
                    LocalePriorityList.add(ACCEPT_LANGUAGE_HTTP[i]).build().getULocales();
            ULocale[] desiredArray = desiredSet.toArray(new ULocale[desiredSet.size()]);
            ULocale n = ULocale.acceptLanguage(desiredArray, r);
            if ((n == null) && (expectLocale != null)) {
                errln("#" + i + ": result was null!");
                continue;
            }
            if (((n == null) && (expectLocale == null)) || (n.toString().equals(expectLocale))) {
                logln("#" + i + ": locale: OK.");
            } else {
                errln("#" + i + ": expected " + expectLocale + " but got " + n.toString());
            }
            Boolean actualBoolean = r[0];
            if (expectBoolean.equals(actualBoolean)) {
                logln("#" + i + ": fallback: OK.");
            } else {
                errln("#" + i + ": fallback: was " + actualBoolean + " expected " + expectBoolean);
            }
        }
    }

    @Test
    public void TestOrientation() {
        {
            String toTest[][] = {
                {"ar", "right-to-left", "top-to-bottom"},
                {"ar_Arab", "right-to-left", "top-to-bottom"},
                {"fa", "right-to-left", "top-to-bottom"},
                {"he", "right-to-left", "top-to-bottom"},
                {"ps", "right-to-left", "top-to-bottom"},
                {"ur", "right-to-left", "top-to-bottom"},
                {"en", "left-to-right", "top-to-bottom"}
            };

            for (int i = 0; i < toTest.length; ++i) {
                ULocale loc = new ULocale(toTest[i][0]);
                String co = loc.getCharacterOrientation();
                String lo = loc.getLineOrientation();
                if (!co.equals(toTest[i][1])) {
                    errln(
                            "Locale \""
                                    + toTest[i][0]
                                    + "\" should have \""
                                    + toTest[i][1]
                                    + "\" character orientation, but got \'"
                                    + co
                                    + "\"");
                } else if (!lo.equals(toTest[i][2])) {
                    errln(
                            "Locale \""
                                    + toTest[i][0]
                                    + "\" should have \""
                                    + toTest[i][2]
                                    + "\" line orientation, but got \'"
                                    + lo
                                    + "\"");
                }
            }
        }
    }

    @Test
    public void TestJB3962() {
        ULocale loc = new ULocale("de_CH");
        String disp = loc.getDisplayName(ULocale.GERMAN);
        if (!disp.equals("Deutsch (Schweiz)")) {
            errln("Did not get the expected display name for de_CH locale. Got: " + prettify(disp));
        }
    }

    @Test
    public void TestMinimize() {
        String[][] data = {
            // source, favorRegion, favorScript
            {"zh-Hans-CN", "zh", "zh"},
            {"zh-Hant-TW", "zh-TW", "zh-Hant"},
            {"zh-Hant-SG", "zh-Hant-SG", "zh-Hant-SG"},
            {"zh-Hans-SG", "zh-SG", "zh-SG"},
            {"zh-Hant-HK", "zh-HK", "zh-HK"},
            {"en_Latn_US", "en", "en"},
            {"en_Cyrl-US", "en-Cyrl", "en-Cyrl"},
            {"en_Cyrl-RU", "en-Cyrl-RU", "en-Cyrl-RU"},
            {"en_Latn-RU", "en-RU", "en-RU"},
            {"sr_Cyrl-US", "sr-US", "sr-US"},
            {"sr_Cyrl-RU", "sr-RU", "sr-RU"},
            {"sr_Latn-RU", "sr_Latn-RU", "sr_Latn-RU"},
        };
        for (String[] test : data) {
            ULocale source = new ULocale(test[0]);
            ULocale expectedFavorRegion = new ULocale(test[1]);
            ULocale expectedFavorScript = new ULocale(test[2]);
            assertEquals(
                    "favor region:\t" + Arrays.asList(test).toString(),
                    expectedFavorRegion,
                    ULocale.minimizeSubtags(source, Minimize.FAVOR_REGION));
            assertEquals(
                    "favor script:\t" + Arrays.asList(test).toString(),
                    expectedFavorScript,
                    ULocale.minimizeSubtags(source, Minimize.FAVOR_SCRIPT));
        }
    }

    @Test
    public void TestAddLikelySubtags() {
        String[][] data = {
            {"en", "en_Latn_US"},
            {"en_US_BOSTON", "en_Latn_US_BOSTON"},
            {"th@calendar=buddhist", "th_Thai_TH@calendar=buddhist"},
            {"ar_ZZ", "ar_Arab_EG"},
            {"zh", "zh_Hans_CN"},
            {"zh_TW", "zh_Hant_TW"},
            {"zh_HK", "zh_Hant_HK"},
            {"zh_Hant", "zh_Hant_TW"},
            {"zh_Zzzz_CN", "zh_Hans_CN"},
            {"und_US", "en_Latn_US"},
            {"und_HK", "zh_Hant_HK"},
            /* Not yet implemented
            {"art_lojban", "arg_lojban"},
            {"zh_cmn_Hans", "zh_cmn_Hans"},
                 */
        };
        for (int i = 0; i < data.length; i++) {
            ULocale org = new ULocale(data[i][0]);
            ULocale res = ULocale.addLikelySubtags(org);
            if (!res.toString().equals(data[i][1])) {
                errln(
                        "Original: "
                                + data[i][0]
                                + " Expected: "
                                + data[i][1]
                                + " - but got "
                                + res.toString());
            }
        }

        String[][] basic_maximize_data = {
            {
                "zu_Zzzz_Zz", "zu_Latn_ZA",
            },
            {"zu_Zz", "zu_Latn_ZA"},
            {"en_Zz", "en_Latn_US"},
            {"en_Kore", "en_Kore_US"},
            {"en_Kore_Zz", "en_Kore_US"},
            {"en_Kore_ZA", "en_Kore_ZA"},
            {"en_Kore_ZA_POSIX", "en_Kore_ZA_POSIX"},
            {"en_Gujr", "en_Gujr_US"},
            {"en_ZA", "en_Latn_ZA"},
            {"en_Gujr_Zz", "en_Gujr_US"},
            {"en_Gujr_ZA", "en_Gujr_ZA"},
            {"en_Gujr_ZA_POSIX", "en_Gujr_ZA_POSIX"},
            {"en_US_POSIX_1901", "en_Latn_US_POSIX_1901"},
            {"en_Latn__POSIX_1901", "en_Latn_US_POSIX_1901"},
            {"en__POSIX_1901", "en_Latn_US_POSIX_1901"},
            {"de__POSIX_1901", "de_Latn_DE_POSIX_1901"},
            {"zzz", ""},
            {
                // ICU-22547
                // unicode_language_id = "root" |
                //   (unicode_language_subtag (sep unicode_script_subtag)?  | unicode_script_subtag)
                //     (sep unicode_region_subtag)?  (sep unicode_variant_subtag)* ;
                // so "aaaa" is a well-formed unicode_language_id
                "aaaa", "aaaa",
            },
            {
                // ICU-22727
                // unicode_language_subtag = alpha{2,3} | alpha{5,8};
                // so "bbbbb", "cccccc", "ddddddd", "eeeeeeee" are
                // well-formed unicode_language_subtag and therefore
                // well-formed unicode_language_id
                "bbbbb", "bbbbb",
            },
            {
                // ICU-22727
                "cccccc", "cccccc",
            },
            {
                // ICU-22727
                "ddddddd", "ddddddd",
            },
            {
                // ICU-22727
                "eeeeeeee", "eeeeeeee",
            },
            {
                // ICU-22546
                "und-Zzzz", "en_Latn_US" // If change, please also update ULocale.java
            },
            {
                // ICU-22546
                "en", "en_Latn_US" // If change, please also update ULocale.java
            },
            {
                // ICU-22546
                "de", "de_Latn_DE" // If change, please also update ULocale.java
            },
            {
                // ICU-22546
                "sr", "sr_Cyrl_RS" // If change, please also update ULocale.java
            },
            {
                // ICU-22546
                "sh", "sh" // If change, please also update ULocale.java
            },
            {
                // ICU-22546
                "zh_Hani", "zh_Hani_CN" // If change, please also update ULocale.java
            },
            {
                // ICU-22545 & ICU-22742
                "en_XA", "en_Latn_XA",
            },
            {
                // ICU-22545 & ICU-22742
                "ar_XB", "ar_Arab_XB",
            },
            {
                // ICU-22545 & ICU-22742
                "ru_XC", "ru_Cyrl_XC",
            },
            {
                // ICU-22742
                "en_PSACCENT", "en_Latn_US_PSACCENT",
            },
            {
                "ar_PSBIDI", "ar_Arab_EG_PSBIDI",
            },
            {
                "ru_PSCRACK", "ru_Cyrl_RU_PSCRACK",
            },
            {
                "ar_PSACCENT", "ar_Arab_EG_PSACCENT",
            },
            {
                "ru_PSBIDI", "ru_Cyrl_RU_PSBIDI",
            },
            {
                "en_PSCRACK", "en_Latn_US_PSCRACK",
            }
        };

        for (int i = 0; i < basic_maximize_data.length; i++) {
            ULocale org = new ULocale(basic_maximize_data[i][0]);
            ULocale res = ULocale.addLikelySubtags(org);
            String exp = basic_maximize_data[i][1];
            if (exp.length() == 0) {
                if (!org.equals(res)) {
                    errln(
                            "Original: "
                                    + basic_maximize_data[i][0]
                                    + " expected: "
                                    + exp
                                    + " - but got "
                                    + res.toString());
                }
            } else if (!res.toString().equals(exp)) {
                errln(
                        "Original: "
                                + basic_maximize_data[i][0]
                                + " expected: "
                                + exp
                                + " - but got "
                                + res.toString());
            }
        }

        String[][] basic_minimize_data = {
            {"en_Latn_US", "en"},
            {"en_Latn_US_POSIX_1901", "en__POSIX_1901"},
            {"en_Zzzz_US_POSIX_1901", "en__POSIX_1901"},
            {"de_Latn_DE_POSIX_1901", "de__POSIX_1901"},
            {"und", "en"}
        };

        for (int i = 0; i < basic_minimize_data.length; i++) {
            ULocale org = new ULocale(basic_minimize_data[i][0]);
            ULocale res = ULocale.minimizeSubtags(org);
            String exp = basic_minimize_data[i][1];
            if (exp.length() == 0) {
                if (!org.equals(res)) {
                    errln(
                            "Original: "
                                    + basic_minimize_data[i][0]
                                    + " expected: "
                                    + exp
                                    + " - but got "
                                    + res.toString());
                }
            } else if (!res.toString().equals(exp)) {
                errln(
                        "Original: "
                                + basic_minimize_data[i][0]
                                + " expected: "
                                + exp
                                + " - but got "
                                + res.toString());
            }
        }

        String[][] full_data = {
            {
                /*   "FROM", */
                /*   "ADD-LIKELY", */
                /*   "REMOVE-LIKELY" */
                /* }, { */
                "aa", "aa_Latn_ET", "aa"
            },
            {"af", "af_Latn_ZA", "af"},
            {"ak", "ak_Latn_GH", "ak"},
            {"am", "am_Ethi_ET", "am"},
            {"ar", "ar_Arab_EG", "ar"},
            {"as", "as_Beng_IN", "as"},
            {"az", "az_Latn_AZ", "az"},
            {"be", "be_Cyrl_BY", "be"},
            {"bg", "bg_Cyrl_BG", "bg"},
            {"bn", "bn_Beng_BD", "bn"},
            {"bo", "bo_Tibt_CN", "bo"},
            {"bs", "bs_Latn_BA", "bs"},
            {"ca", "ca_Latn_ES", "ca"},
            {"ch", "ch_Latn_GU", "ch"},
            {"chk", "chk_Latn_FM", "chk"},
            {"cs", "cs_Latn_CZ", "cs"},
            {"cy", "cy_Latn_GB", "cy"},
            {"da", "da_Latn_DK", "da"},
            {"de", "de_Latn_DE", "de"},
            {"dv", "dv_Thaa_MV", "dv"},
            {"dz", "dz_Tibt_BT", "dz"},
            {"ee", "ee_Latn_GH", "ee"},
            {"el", "el_Grek_GR", "el"},
            {"en", "en_Latn_US", "en"},
            {"es", "es_Latn_ES", "es"},
            {"et", "et_Latn_EE", "et"},
            {"eu", "eu_Latn_ES", "eu"},
            {"fa", "fa_Arab_IR", "fa"},
            {"fi", "fi_Latn_FI", "fi"},
            {"fil", "fil_Latn_PH", "fil"},
            {"fj", "fj_Latn_FJ", "fj"},
            {"fo", "fo_Latn_FO", "fo"},
            {"fr", "fr_Latn_FR", "fr"},
            {"fur", "fur_Latn_IT", "fur"},
            {"ga", "ga_Latn_IE", "ga"},
            {"gaa", "gaa_Latn_GH", "gaa"},
            {"gl", "gl_Latn_ES", "gl"},
            {"gn", "gn_Latn_PY", "gn"},
            {"gu", "gu_Gujr_IN", "gu"},
            {"ha", "ha_Latn_NG", "ha"},
            {"haw", "haw_Latn_US", "haw"},
            {"he", "he_Hebr_IL", "he"},
            {"hi", "hi_Deva_IN", "hi"},
            {"hr", "hr_Latn_HR", "hr"},
            {"ht", "ht_Latn_HT", "ht"},
            {"hu", "hu_Latn_HU", "hu"},
            {"hy", "hy_Armn_AM", "hy"},
            {"id", "id_Latn_ID", "id"},
            {"ig", "ig_Latn_NG", "ig"},
            {"ii", "ii_Yiii_CN", "ii"},
            {"is", "is_Latn_IS", "is"},
            {"it", "it_Latn_IT", "it"},
            {"ja", "ja_Jpan_JP", "ja"},
            {"ka", "ka_Geor_GE", "ka"},
            {"kaj", "kaj_Latn_NG", "kaj"},
            {"kam", "kam_Latn_KE", "kam"},
            {"kk", "kk_Cyrl_KZ", "kk"},
            {"kl", "kl_Latn_GL", "kl"},
            {"km", "km_Khmr_KH", "km"},
            {"kn", "kn_Knda_IN", "kn"},
            {"ko", "ko_Kore_KR", "ko"},
            {"kok", "kok_Deva_IN", "kok"},
            {"kpe", "kpe_Latn_LR", "kpe"},
            {"ku", "ku_Latn_TR", "ku"},
            {"ky", "ky_Cyrl_KG", "ky"},
            {"la", "la_Latn_VA", "la"},
            {"ln", "ln_Latn_CD", "ln"},
            {"lo", "lo_Laoo_LA", "lo"},
            {"lt", "lt_Latn_LT", "lt"},
            {"lv", "lv_Latn_LV", "lv"},
            {"mg", "mg_Latn_MG", "mg"},
            {"mh", "mh_Latn_MH", "mh"},
            {"mk", "mk_Cyrl_MK", "mk"},
            {"ml", "ml_Mlym_IN", "ml"},
            {"mn", "mn_Cyrl_MN", "mn"},
            {"mr", "mr_Deva_IN", "mr"},
            {"ms", "ms_Latn_MY", "ms"},
            {"mt", "mt_Latn_MT", "mt"},
            {"my", "my_Mymr_MM", "my"},
            {"na", "na_Latn_NR", "na"},
            {"ne", "ne_Deva_NP", "ne"},
            {"niu", "niu_Latn_NU", "niu"},
            {"nl", "nl_Latn_NL", "nl"},
            {"nn", "nn_Latn_NO", "nn"},
            {"no", "no_Latn_NO", "no"},
            {"nr", "nr_Latn_ZA", "nr"},
            {"nso", "nso_Latn_ZA", "nso"},
            {"om", "om_Latn_ET", "om"},
            {"or", "or_Orya_IN", "or"},
            {"pa", "pa_Guru_IN", "pa"},
            {"pa_Arab", "pa_Arab_PK", "pa_PK"},
            {"pa_PK", "pa_Arab_PK", "pa_PK"},
            {"pap", "pap_Latn_CW", "pap"},
            {"pau", "pau_Latn_PW", "pau"},
            {"pl", "pl_Latn_PL", "pl"},
            {"ps", "ps_Arab_AF", "ps"},
            {"pt", "pt_Latn_BR", "pt"},
            {"rn", "rn_Latn_BI", "rn"},
            {"ro", "ro_Latn_RO", "ro"},
            {"ru", "ru_Cyrl_RU", "ru"},
            {"rw", "rw_Latn_RW", "rw"},
            {"sa", "sa_Deva_IN", "sa"},
            {"se", "se_Latn_NO", "se"},
            {"sg", "sg_Latn_CF", "sg"},
            {"si", "si_Sinh_LK", "si"},
            {"sid", "sid_Latn_ET", "sid"},
            {"sk", "sk_Latn_SK", "sk"},
            {"sl", "sl_Latn_SI", "sl"},
            {"sm", "sm_Latn_WS", "sm"},
            {"so", "so_Latn_SO", "so"},
            {"sq", "sq_Latn_AL", "sq"},
            {"sr", "sr_Cyrl_RS", "sr"},
            {"ss", "ss_Latn_ZA", "ss"},
            {"st", "st_Latn_ZA", "st"},
            {"sv", "sv_Latn_SE", "sv"},
            {"sw", "sw_Latn_TZ", "sw"},
            {"ta", "ta_Taml_IN", "ta"},
            {"te", "te_Telu_IN", "te"},
            {"tet", "tet_Latn_TL", "tet"},
            {"tg", "tg_Cyrl_TJ", "tg"},
            {"th", "th_Thai_TH", "th"},
            {"ti", "ti_Ethi_ET", "ti"},
            {"tig", "tig_Ethi_ER", "tig"},
            {"tk", "tk_Latn_TM", "tk"},
            {"tkl", "tkl_Latn_TK", "tkl"},
            {"tn", "tn_Latn_ZA", "tn"},
            {"to", "to_Latn_TO", "to"},
            {"tpi", "tpi_Latn_PG", "tpi"},
            {"tr", "tr_Latn_TR", "tr"},
            {"ts", "ts_Latn_ZA", "ts"},
            {"tt", "tt_Cyrl_RU", "tt"},
            {"tvl", "tvl_Latn_TV", "tvl"},
            {"ty", "ty_Latn_PF", "ty"},
            {"uk", "uk_Cyrl_UA", "uk"},
            {"und", "en_Latn_US", "en"},
            {"und_AD", "ca_Latn_AD", "ca_AD"},
            {"und_AE", "ar_Arab_AE", "ar_AE"},
            {"und_AF", "fa_Arab_AF", "fa_AF"},
            {"und_AL", "sq_Latn_AL", "sq"},
            {"und_AM", "hy_Armn_AM", "hy"},
            {"und_AO", "pt_Latn_AO", "pt_AO"},
            {"und_AR", "es_Latn_AR", "es_AR"},
            {"und_AS", "sm_Latn_AS", "sm_AS"},
            {"und_AT", "de_Latn_AT", "de_AT"},
            {"und_AW", "nl_Latn_AW", "nl_AW"},
            {"und_AX", "sv_Latn_AX", "sv_AX"},
            {"und_AZ", "az_Latn_AZ", "az"},
            {"und_Arab", "ar_Arab_EG", "ar"},
            {"und_Arab_IN", "ur_Arab_IN", "ur_IN"},
            {"und_Arab_PK", "ur_Arab_PK", "ur"},
            {"und_Arab_SN", "ar_Arab_SN", "ar_SN"},
            {"und_Armn", "hy_Armn_AM", "hy"},
            {"und_BA", "bs_Latn_BA", "bs"},
            {"und_BD", "bn_Beng_BD", "bn"},
            {"und_BE", "nl_Latn_BE", "nl_BE"},
            {"und_BF", "fr_Latn_BF", "fr_BF"},
            {"und_BG", "bg_Cyrl_BG", "bg"},
            {"und_BH", "ar_Arab_BH", "ar_BH"},
            {"und_BI", "rn_Latn_BI", "rn"},
            {"und_BJ", "fr_Latn_BJ", "fr_BJ"},
            {"und_BN", "ms_Latn_BN", "ms_BN"},
            {"und_BO", "es_Latn_BO", "es_BO"},
            {"und_BR", "pt_Latn_BR", "pt"},
            {"und_BT", "dz_Tibt_BT", "dz"},
            {"und_BY", "ru_Cyrl_BY", "ru_BY"},
            {"und_Beng", "bn_Beng_BD", "bn"},
            {"und_Beng_IN", "bn_Beng_IN", "bn_IN"},
            {"und_CD", "fr_Latn_CD", "fr_CD"},
            {"und_CF", "sg_Latn_CF", "sg"},
            {"und_CG", "fr_Latn_CG", "fr_CG"},
            {"und_CH", "de_Latn_CH", "de_CH"},
            {"und_CI", "fr_Latn_CI", "fr_CI"},
            {"und_CL", "es_Latn_CL", "es_CL"},
            {"und_CM", "fr_Latn_CM", "fr_CM"},
            {"und_CN", "zh_Hans_CN", "zh"},
            {"und_CO", "es_Latn_CO", "es_CO"},
            {"und_CR", "es_Latn_CR", "es_CR"},
            {"und_CU", "es_Latn_CU", "es_CU"},
            {"und_CV", "pt_Latn_CV", "pt_CV"},
            {"und_CY", "el_Grek_CY", "el_CY"},
            {"und_CZ", "cs_Latn_CZ", "cs"},
            {"und_Cyrl", "ru_Cyrl_RU", "ru"},
            {"und_Cyrl_KZ", "ru_Cyrl_KZ", "ru_KZ"},
            {"und_DE", "de_Latn_DE", "de"},
            {"und_DJ", "fr_Latn_DJ", "fr_DJ"},
            {"und_DK", "da_Latn_DK", "da"},
            {"und_DO", "es_Latn_DO", "es_DO"},
            {"und_DZ", "ar_Arab_DZ", "ar_DZ"},
            {"und_Deva", "hi_Deva_IN", "hi"},
            {"und_EC", "es_Latn_EC", "es_EC"},
            {"und_EE", "et_Latn_EE", "et"},
            {"und_EG", "ar_Arab_EG", "ar"},
            {"und_EH", "ar_Arab_EH", "ar_EH"},
            {"und_ER", "ti_Ethi_ER", "ti_ER"},
            {"und_ES", "es_Latn_ES", "es"},
            {"und_ET", "am_Ethi_ET", "am"},
            {"und_Ethi", "am_Ethi_ET", "am"},
            {"und_Ethi_ER", "ti_Ethi_ER", "ti_ER"},
            {"und_FI", "fi_Latn_FI", "fi"},
            {"und_FM", "en_Latn_FM", "en_FM"},
            {"und_FO", "fo_Latn_FO", "fo"},
            {"und_FR", "fr_Latn_FR", "fr"},
            {"und_GA", "fr_Latn_GA", "fr_GA"},
            {"und_GE", "ka_Geor_GE", "ka"},
            {"und_GF", "fr_Latn_GF", "fr_GF"},
            {"und_GL", "kl_Latn_GL", "kl"},
            {"und_GN", "fr_Latn_GN", "fr_GN"},
            {"und_GP", "fr_Latn_GP", "fr_GP"},
            {"und_GQ", "es_Latn_GQ", "es_GQ"},
            {"und_GR", "el_Grek_GR", "el"},
            {"und_GT", "es_Latn_GT", "es_GT"},
            {"und_GU", "en_Latn_GU", "en_GU"},
            {"und_GW", "pt_Latn_GW", "pt_GW"},
            {"und_Geor", "ka_Geor_GE", "ka"},
            {"und_Grek", "el_Grek_GR", "el"},
            {"und_Gujr", "gu_Gujr_IN", "gu"},
            {"und_Guru", "pa_Guru_IN", "pa"},
            {"und_HK", "zh_Hant_HK", "zh_HK"},
            {"und_HN", "es_Latn_HN", "es_HN"},
            {"und_HR", "hr_Latn_HR", "hr"},
            {"und_HT", "ht_Latn_HT", "ht"},
            {"und_HU", "hu_Latn_HU", "hu"},
            {"und_Hani", "zh_Hani_CN", "zh_Hani"},
            {"und_Hans", "zh_Hans_CN", "zh"},
            {"und_Hant", "zh_Hant_TW", "zh_TW"},
            {"und_Hebr", "he_Hebr_IL", "he"},
            {"und_ID", "id_Latn_ID", "id"},
            {"und_IL", "he_Hebr_IL", "he"},
            {"und_IN", "hi_Deva_IN", "hi"},
            {"und_IQ", "ar_Arab_IQ", "ar_IQ"},
            {"und_IR", "fa_Arab_IR", "fa"},
            {"und_IS", "is_Latn_IS", "is"},
            {"und_IT", "it_Latn_IT", "it"},
            {"und_JO", "ar_Arab_JO", "ar_JO"},
            {"und_JP", "ja_Jpan_JP", "ja"},
            {"und_Jpan", "ja_Jpan_JP", "ja"},
            {"und_KG", "ky_Cyrl_KG", "ky"},
            {"und_KH", "km_Khmr_KH", "km"},
            {"und_KM", "ar_Arab_KM", "ar_KM"},
            {"und_KP", "ko_Kore_KP", "ko_KP"},
            {"und_KR", "ko_Kore_KR", "ko"},
            {"und_KW", "ar_Arab_KW", "ar_KW"},
            {"und_KZ", "ru_Cyrl_KZ", "ru_KZ"},
            {"und_Khmr", "km_Khmr_KH", "km"},
            {"und_Knda", "kn_Knda_IN", "kn"},
            {"und_Kore", "ko_Kore_KR", "ko"},
            {"und_LA", "lo_Laoo_LA", "lo"},
            {"und_LB", "ar_Arab_LB", "ar_LB"},
            {"und_LI", "de_Latn_LI", "de_LI"},
            {"und_LK", "si_Sinh_LK", "si"},
            {"und_LS", "st_Latn_LS", "st_LS"},
            {"und_LT", "lt_Latn_LT", "lt"},
            {"und_LU", "fr_Latn_LU", "fr_LU"},
            {"und_LV", "lv_Latn_LV", "lv"},
            {"und_LY", "ar_Arab_LY", "ar_LY"},
            {"und_Laoo", "lo_Laoo_LA", "lo"},
            {"und_Latn_ES", "es_Latn_ES", "es"},
            {"und_Latn_ET", "en_Latn_ET", "en_ET"},
            {"und_Latn_GB", "en_Latn_GB", "en_GB"},
            {"und_Latn_GH", "ak_Latn_GH", "ak"},
            {"und_Latn_ID", "id_Latn_ID", "id"},
            {"und_Latn_IT", "it_Latn_IT", "it"},
            {"und_Latn_NG", "en_Latn_NG", "en_NG"},
            {"und_Latn_TR", "tr_Latn_TR", "tr"},
            {"und_Latn_ZA", "en_Latn_ZA", "en_ZA"},
            {"und_MA", "ar_Arab_MA", "ar_MA"},
            {"und_MC", "fr_Latn_MC", "fr_MC"},
            {"und_MD", "ro_Latn_MD", "ro_MD"},
            {"und_ME", "sr_Latn_ME", "sr_ME"},
            {"und_MG", "mg_Latn_MG", "mg"},
            {"und_MK", "mk_Cyrl_MK", "mk"},
            {"und_ML", "bm_Latn_ML", "bm"},
            {"und_MM", "my_Mymr_MM", "my"},
            {"und_MN", "mn_Cyrl_MN", "mn"},
            {"und_MO", "zh_Hant_MO", "zh_MO"},
            {"und_MQ", "fr_Latn_MQ", "fr_MQ"},
            {"und_MR", "ar_Arab_MR", "ar_MR"},
            {"und_MT", "mt_Latn_MT", "mt"},
            {"und_MV", "dv_Thaa_MV", "dv"},
            {"und_MX", "es_Latn_MX", "es_MX"},
            {"und_MY", "ms_Latn_MY", "ms"},
            {"und_MZ", "pt_Latn_MZ", "pt_MZ"},
            {"und_Mlym", "ml_Mlym_IN", "ml"},
            {"und_Mymr", "my_Mymr_MM", "my"},
            {"und_NC", "fr_Latn_NC", "fr_NC"},
            {"und_NE", "ha_Latn_NE", "ha_NE"},
            {"und_NG", "en_Latn_NG", "en_NG"},
            {"und_NI", "es_Latn_NI", "es_NI"},
            {"und_NL", "nl_Latn_NL", "nl"},
            {"und_NO", "nb_Latn_NO", "nb"},
            {"und_NP", "ne_Deva_NP", "ne"},
            {"und_NR", "en_Latn_NR", "en_NR"},
            {"und_OM", "ar_Arab_OM", "ar_OM"},
            {"und_Orya", "or_Orya_IN", "or"},
            {"und_PA", "es_Latn_PA", "es_PA"},
            {"und_PE", "es_Latn_PE", "es_PE"},
            {"und_PF", "fr_Latn_PF", "fr_PF"},
            {"und_PG", "tpi_Latn_PG", "tpi"},
            {"und_PH", "fil_Latn_PH", "fil"},
            {"und_PL", "pl_Latn_PL", "pl"},
            {"und_PM", "fr_Latn_PM", "fr_PM"},
            {"und_PR", "es_Latn_PR", "es_PR"},
            {"und_PS", "ar_Arab_PS", "ar_PS"},
            {"und_PT", "pt_Latn_PT", "pt_PT"},
            {"und_PW", "pau_Latn_PW", "pau"},
            {"und_PY", "gn_Latn_PY", "gn"},
            {"und_QA", "ar_Arab_QA", "ar_QA"},
            {"und_RE", "fr_Latn_RE", "fr_RE"},
            {"und_RO", "ro_Latn_RO", "ro"},
            {"und_RS", "sr_Cyrl_RS", "sr"},
            {"und_RU", "ru_Cyrl_RU", "ru"},
            {"und_RW", "rw_Latn_RW", "rw"},
            {"und_SA", "ar_Arab_SA", "ar_SA"},
            {"und_SD", "ar_Arab_SD", "ar_SD"},
            {"und_SE", "sv_Latn_SE", "sv"},
            {"und_SG", "en_Latn_SG", "en_SG"},
            {"und_SI", "sl_Latn_SI", "sl"},
            {"und_SJ", "nb_Latn_SJ", "nb_SJ"},
            {"und_SK", "sk_Latn_SK", "sk"},
            {"und_SM", "it_Latn_SM", "it_SM"},
            {"und_SN", "wo_Latn_SN", "wo"},
            {"und_SO", "so_Latn_SO", "so"},
            {"und_SR", "nl_Latn_SR", "nl_SR"},
            {"und_ST", "pt_Latn_ST", "pt_ST"},
            {"und_SV", "es_Latn_SV", "es_SV"},
            {"und_SY", "ar_Arab_SY", "ar_SY"},
            {"und_Sinh", "si_Sinh_LK", "si"},
            {"und_Syrc", "syr_Syrc_IQ", "syr"},
            {"und_TD", "ar_Arab_TD", "ar_TD"},
            {"und_TG", "fr_Latn_TG", "fr_TG"},
            {"und_TH", "th_Thai_TH", "th"},
            {"und_TJ", "tg_Cyrl_TJ", "tg"},
            {"und_TK", "tkl_Latn_TK", "tkl"},
            {"und_TL", "pt_Latn_TL", "pt_TL"},
            {"und_TM", "tk_Latn_TM", "tk"},
            {"und_TN", "ar_Arab_TN", "ar_TN"},
            {"und_TO", "to_Latn_TO", "to"},
            {"und_TR", "tr_Latn_TR", "tr"},
            {"und_TV", "tvl_Latn_TV", "tvl"},
            {"und_TW", "zh_Hant_TW", "zh_TW"},
            {"und_Taml", "ta_Taml_IN", "ta"},
            {"und_Telu", "te_Telu_IN", "te"},
            {"und_Thaa", "dv_Thaa_MV", "dv"},
            {"und_Thai", "th_Thai_TH", "th"},
            {"und_Tibt", "bo_Tibt_CN", "bo"},
            {"und_UA", "uk_Cyrl_UA", "uk"},
            {"und_UY", "es_Latn_UY", "es_UY"},
            {"und_UZ", "uz_Latn_UZ", "uz"},
            {"und_VA", "it_Latn_VA", "it_VA"},
            {"und_VE", "es_Latn_VE", "es_VE"},
            {"und_VN", "vi_Latn_VN", "vi"},
            {"und_VU", "bi_Latn_VU", "bi"},
            {"und_WF", "fr_Latn_WF", "fr_WF"},
            {"und_WS", "sm_Latn_WS", "sm"},
            {"und_YE", "ar_Arab_YE", "ar_YE"},
            {"und_YT", "fr_Latn_YT", "fr_YT"},
            {"und_Yiii", "ii_Yiii_CN", "ii"},
            {"ur", "ur_Arab_PK", "ur"},
            {"uz", "uz_Latn_UZ", "uz"},
            {"uz_AF", "uz_Arab_AF", "uz_AF"},
            {"uz_Arab", "uz_Arab_AF", "uz_AF"},
            {"ve", "ve_Latn_ZA", "ve"},
            {"vi", "vi_Latn_VN", "vi"},
            {"wal", "wal_Ethi_ET", "wal"},
            {"wo", "wo_Latn_SN", "wo"},
            {"wo_SN", "wo_Latn_SN", "wo"},
            {"xh", "xh_Latn_ZA", "xh"},
            {"yo", "yo_Latn_NG", "yo"},
            {"zh", "zh_Hans_CN", "zh"},
            {"zh_HK", "zh_Hant_HK", "zh_HK"},
            {"zh_Hani", "zh_Hani_CN", "zh_Hani"},
            {"zh_Hant", "zh_Hant_TW", "zh_TW"},
            {"zh_MO", "zh_Hant_MO", "zh_MO"},
            {"zh_TW", "zh_Hant_TW", "zh_TW"},
            {"zu", "zu_Latn_ZA", "zu"},
            {"und", "en_Latn_US", "en"},
            {"und_ZZ", "en_Latn_US", "en"},
            {"und_CN", "zh_Hans_CN", "zh"},
            {"und_TW", "zh_Hant_TW", "zh_TW"},
            {"und_HK", "zh_Hant_HK", "zh_HK"},
            {"und_AQ", "en_Latn_AQ", "en_AQ"},
            {"und_Zzzz", "en_Latn_US", "en"},
            {"und_Zzzz_ZZ", "en_Latn_US", "en"},
            {"und_Zzzz_CN", "zh_Hans_CN", "zh"},
            {"und_Zzzz_TW", "zh_Hant_TW", "zh_TW"},
            {"und_Zzzz_HK", "zh_Hant_HK", "zh_HK"},
            {"und_Zzzz_AQ", "en_Latn_AQ", "en_AQ"},
            {"und_Latn", "en_Latn_US", "en"},
            {"und_Latn_ZZ", "en_Latn_US", "en"},
            {"und_Latn_CN", "za_Latn_CN", "za"},
            {"und_Latn_TW", "trv_Latn_TW", "trv"},
            {"und_Latn_HK", "en_Latn_HK", "en_HK"},
            {"und_Latn_AQ", "en_Latn_AQ", "en_AQ"},
            {"und_Hans", "zh_Hans_CN", "zh"},
            {"und_Hans_ZZ", "zh_Hans_CN", "zh"},
            {"und_Hans_CN", "zh_Hans_CN", "zh"},
            {"und_Hans_TW", "zh_Hans_TW", "zh_Hans_TW"},
            {"und_Hans_HK", "zh_Hans_HK", "zh_Hans_HK"},
            {"und_Hans_AQ", "zh_Hans_AQ", "zh_AQ"},
            {"und_Hant", "zh_Hant_TW", "zh_TW"},
            {"und_Hant_ZZ", "zh_Hant_TW", "zh_TW"},
            {"und_Hant_CN", "yue_Hant_CN", "yue_Hant_CN"},
            {"und_Hant_TW", "zh_Hant_TW", "zh_TW"},
            {"und_Hant_HK", "zh_Hant_HK", "zh_HK"},
            {"und_Hant_AQ", "zh_Hant_AQ", "zh_Hant_AQ"},
            {"und_Moon", "en_Moon_US", "en_Moon"},
            {"und_Moon_ZZ", "en_Moon_US", "en_Moon"},
            {"und_Moon_CN", "zh_Moon_CN", "zh_Moon"},
            {"und_Moon_TW", "zh_Moon_TW", "zh_Moon_TW"},
            {"und_Moon_HK", "zh_Moon_HK", "zh_Moon_HK"},
            {"und_Moon_AQ", "en_Moon_AQ", "en_Moon_AQ"},
            {"es", "es_Latn_ES", "es"},
            {"es_ZZ", "es_Latn_ES", "es"},
            {"es_CN", "es_Latn_CN", "es_CN"},
            {"es_TW", "es_Latn_TW", "es_TW"},
            {"es_HK", "es_Latn_HK", "es_HK"},
            {"es_AQ", "es_Latn_AQ", "es_AQ"},
            {"es_Zzzz", "es_Latn_ES", "es"},
            {"es_Zzzz_ZZ", "es_Latn_ES", "es"},
            {"es_Zzzz_CN", "es_Latn_CN", "es_CN"},
            {"es_Zzzz_TW", "es_Latn_TW", "es_TW"},
            {"es_Zzzz_HK", "es_Latn_HK", "es_HK"},
            {"es_Zzzz_AQ", "es_Latn_AQ", "es_AQ"},
            {"es_Latn", "es_Latn_ES", "es"},
            {"es_Latn_ZZ", "es_Latn_ES", "es"},
            {"es_Latn_CN", "es_Latn_CN", "es_CN"},
            {"es_Latn_TW", "es_Latn_TW", "es_TW"},
            {"es_Latn_HK", "es_Latn_HK", "es_HK"},
            {"es_Latn_AQ", "es_Latn_AQ", "es_AQ"},
            {"es_Hans", "es_Hans_ES", "es_Hans"},
            {"es_Hans_ZZ", "es_Hans_ES", "es_Hans"},
            {"es_Hans_CN", "es_Hans_CN", "es_Hans_CN"},
            {"es_Hans_TW", "es_Hans_TW", "es_Hans_TW"},
            {"es_Hans_HK", "es_Hans_HK", "es_Hans_HK"},
            {"es_Hans_AQ", "es_Hans_AQ", "es_Hans_AQ"},
            {"es_Hant", "es_Hant_ES", "es_Hant"},
            {"es_Hant_ZZ", "es_Hant_ES", "es_Hant"},
            {"es_Hant_CN", "es_Hant_CN", "es_Hant_CN"},
            {"es_Hant_TW", "es_Hant_TW", "es_Hant_TW"},
            {"es_Hant_HK", "es_Hant_HK", "es_Hant_HK"},
            {"es_Hant_AQ", "es_Hant_AQ", "es_Hant_AQ"},
            {"es_Moon", "es_Moon_ES", "es_Moon"},
            {"es_Moon_ZZ", "es_Moon_ES", "es_Moon"},
            {"es_Moon_CN", "es_Moon_CN", "es_Moon_CN"},
            {"es_Moon_TW", "es_Moon_TW", "es_Moon_TW"},
            {"es_Moon_HK", "es_Moon_HK", "es_Moon_HK"},
            {"es_Moon_AQ", "es_Moon_AQ", "es_Moon_AQ"},
            {"zh", "zh_Hans_CN", "zh"},
            {"zh_ZZ", "zh_Hans_CN", "zh"},
            {"zh_CN", "zh_Hans_CN", "zh"},
            {"zh_TW", "zh_Hant_TW", "zh_TW"},
            {"zh_HK", "zh_Hant_HK", "zh_HK"},
            {"zh_AQ", "zh_Hans_AQ", "zh_AQ"},
            {"zh_Zzzz", "zh_Hans_CN", "zh"},
            {"zh_Zzzz_ZZ", "zh_Hans_CN", "zh"},
            {"zh_Zzzz_CN", "zh_Hans_CN", "zh"},
            {"zh_Zzzz_TW", "zh_Hant_TW", "zh_TW"},
            {"zh_Zzzz_HK", "zh_Hant_HK", "zh_HK"},
            {"zh_Zzzz_AQ", "zh_Hans_AQ", "zh_AQ"},
            {"zh_Latn", "zh_Latn_CN", "zh_Latn"},
            {"zh_Latn_ZZ", "zh_Latn_CN", "zh_Latn"},
            {"zh_Latn_CN", "zh_Latn_CN", "zh_Latn"},
            {"zh_Latn_TW", "zh_Latn_TW", "zh_Latn_TW"},
            {"zh_Latn_HK", "zh_Latn_HK", "zh_Latn_HK"},
            {"zh_Latn_AQ", "zh_Latn_AQ", "zh_Latn_AQ"},
            {"zh_Hans", "zh_Hans_CN", "zh"},
            {"zh_Hans_ZZ", "zh_Hans_CN", "zh"},
            {"zh_Hans_TW", "zh_Hans_TW", "zh_Hans_TW"},
            {"zh_Hans_HK", "zh_Hans_HK", "zh_Hans_HK"},
            {"zh_Hans_AQ", "zh_Hans_AQ", "zh_AQ"},
            {"zh_Hant", "zh_Hant_TW", "zh_TW"},
            {"zh_Hant_ZZ", "zh_Hant_TW", "zh_TW"},
            {"zh_Hant_CN", "zh_Hant_CN", "zh_Hant_CN"},
            {"zh_Hant_AQ", "zh_Hant_AQ", "zh_Hant_AQ"},
            {"zh_Moon", "zh_Moon_CN", "zh_Moon"},
            {"zh_Moon_ZZ", "zh_Moon_CN", "zh_Moon"},
            {"zh_Moon_CN", "zh_Moon_CN", "zh_Moon"},
            {"zh_Moon_TW", "zh_Moon_TW", "zh_Moon_TW"},
            {"zh_Moon_HK", "zh_Moon_HK", "zh_Moon_HK"},
            {"zh_Moon_AQ", "zh_Moon_AQ", "zh_Moon_AQ"},
            {"art", "", ""},
            {"art_ZZ", "", ""},
            {"art_CN", "", ""},
            {"art_TW", "", ""},
            {"art_HK", "", ""},
            {"art_AQ", "", ""},
            {"art_Zzzz", "", ""},
            {"art_Zzzz_ZZ", "", ""},
            {"art_Zzzz_CN", "", ""},
            {"art_Zzzz_TW", "", ""},
            {"art_Zzzz_HK", "", ""},
            {"art_Zzzz_AQ", "", ""},
            {"art_Latn", "", ""},
            {"art_Latn_ZZ", "", ""},
            {"art_Latn_CN", "", ""},
            {"art_Latn_TW", "", ""},
            {"art_Latn_HK", "", ""},
            {"art_Latn_AQ", "", ""},
            {"art_Hans", "", ""},
            {"art_Hans_ZZ", "", ""},
            {"art_Hans_CN", "", ""},
            {"art_Hans_TW", "", ""},
            {"art_Hans_HK", "", ""},
            {"art_Hans_AQ", "", ""},
            {"art_Hant", "", ""},
            {"art_Hant_ZZ", "", ""},
            {"art_Hant_CN", "", ""},
            {"art_Hant_TW", "", ""},
            {"art_Hant_HK", "", ""},
            {"art_Hant_AQ", "", ""},
            {"art_Moon", "", ""},
            {"art_Moon_ZZ", "", ""},
            {"art_Moon_CN", "", ""},
            {"art_Moon_TW", "", ""},
            {"art_Moon_HK", "", ""},
            {"art_Moon_AQ", "", ""},
            {"aae_Latn_IT", "aae_Latn_IT", "aae"},
            {"aae_Thai_CO", "aae_Thai_CO", "aae_Thai_CO"},
            {"und_CW", "pap_Latn_CW", "pap"},
            {"zh_Hant", "zh_Hant_TW", "zh_TW"},
            {"zh_Hani", "zh_Hani_CN", "zh_Hani"},
            {"und", "en_Latn_US", "en"},
            {"und_Thai", "th_Thai_TH", "th"},
            {"und_419", "es_Latn_419", "es_419"},
            {"und_150", "en_Latn_150", "en_150"},
            {"und_AT", "de_Latn_AT", "de_AT"},
            {"und_US", "en_Latn_US", "en"},
            {
                "th@x=private", "th_Thai_TH@x=private", "th@x=private",
            },
            {
                "und@x=private", "en_Latn_US@x=private", "en@x=private",
            }
        };

        for (int i = 0; i < full_data.length; i++) {
            ULocale org = new ULocale(full_data[i][0]);
            ULocale res = ULocale.addLikelySubtags(org);
            String exp = full_data[i][1];
            if (exp.length() == 0) {
                if (!org.equals(res)) {
                    errln(
                            "Original: "
                                    + full_data[i][0]
                                    + " expected: "
                                    + exp
                                    + " - but got "
                                    + res.toString());
                }
            } else if (!res.toString().equals(exp)) {
                errln(
                        "Original: "
                                + full_data[i][0]
                                + " expected: "
                                + exp
                                + " - but got "
                                + res.toString());
            }
        }

        for (int i = 0; i < full_data.length; i++) {
            String maximal = full_data[i][1];

            if (maximal.length() > 0) {
                ULocale org = new ULocale(maximal);
                ULocale res = ULocale.minimizeSubtags(org);
                String exp = full_data[i][2];
                if (exp.length() == 0) {
                    if (!org.equals(res)) {
                        errln(
                                "Original: "
                                        + full_data[i][1]
                                        + " expected: "
                                        + exp
                                        + " - but got "
                                        + res.toString());
                    }
                } else if (!res.toString().equals(exp)) {
                    errln(
                            "Original: "
                                    + full_data[i][1]
                                    + " expected: "
                                    + exp
                                    + " - but got "
                                    + res.toString());
                }
            }
        }
    }

    @Test
    public void TestCLDRVersion() {
        // VersionInfo zeroVersion = VersionInfo.getInstance(0, 0, 0, 0);
        VersionInfo testExpect;
        VersionInfo testCurrent;
        VersionInfo cldrVersion;

        cldrVersion = LocaleData.getCLDRVersion();

        logln("uloc_getCLDRVersion() returned: '" + cldrVersion + "'");

        // why isn't this public for tests somewhere?
        final ClassLoader testLoader = ICUResourceBundleTest.class.getClassLoader();
        UResourceBundle bundle =
                UResourceBundle.getBundleInstance(
                        "com/ibm/icu/dev/data/testdata", ULocale.ROOT, testLoader);

        testExpect = VersionInfo.getInstance(bundle.getString("ExpectCLDRVersionAtLeast"));
        testCurrent = VersionInfo.getInstance(bundle.getString("CurrentCLDRVersion"));

        logln("(data) ExpectCLDRVersionAtLeast { " + testExpect + "");
        if (cldrVersion.compareTo(testExpect) < 0) {
            errln("CLDR version is too old, expect at least " + testExpect + ".");
        }

        int r = cldrVersion.compareTo(testCurrent);
        if (r < 0) {
            logln(
                    "CLDR version is behind 'current' (for testdata/root.txt) "
                            + testCurrent
                            + ". Some things may fail.\n");
        } else if (r > 0) {
            logln(
                    "CLDR version is ahead of 'current' (for testdata/root.txt) "
                            + testCurrent
                            + ". Some things may fail.\n");
        } else {
            // CLDR version is OK.
        }
    }

    @Test
    public void TestToLanguageTag() {
        final String[][] locale_to_langtag = {
            {"", "und"},
            {"en", "en"},
            {"en_US", "en-US"},
            {"iw_IL", "he-IL"},
            {"sr_Latn_SR", "sr-Latn-SR"},
            {"en_US_POSIX@ca=japanese", "en-US-u-ca-japanese-va-posix"},
            {"en__POSIX", "en-u-va-posix"},
            {
                "en_US_POSIX_VAR", "en-US-posix-x-lvariant-var"
            }, // variant POSIX_VAR is processed as regular variant
            {
                "en_US_VAR_POSIX", "en-US-x-lvariant-var-posix"
            }, // variant VAR_POSIX is processed as regular variant
            {
                "en_US_POSIX@va=posix2", "en-US-u-va-posix2"
            }, // if keyword va=xxx already exists, variant POSIX is simply dropped
            {"und_555", "und-555"},
            {"123", "und"},
            {"%$#&", "und"},
            {"_Latn", "und-Latn"},
            {"_DE", "und-DE"},
            {"und_FR", "und-FR"},
            {"th_TH_TH", "th-TH-x-lvariant-th"},
            {"bogus", "bogus"},
            {"foooobarrr", "und"},
            {"aa_BB_CYRL", "aa-BB-x-lvariant-cyrl"},
            {"en_US_1234", "en-US-1234"},
            {"en_US_VARIANTA_VARIANTB", "en-US-varianta-variantb"},
            {"en_US_VARIANTB_VARIANTA", "en-US-varianta-variantb"}, /* ICU-20478 */
            {"ja__9876_5432", "ja-5432-9876"}, /* ICU-20478 */
            {"zh_Hant__VAR", "zh-Hant-x-lvariant-var"},
            {"es__BADVARIANT_GOODVAR", "es"},
            {"es__GOODVAR_BAD_BADVARIANT", "es-goodvar-x-lvariant-bad"},
            {"en@calendar=gregorian", "en-u-ca-gregory"},
            {"de@collation=phonebook;calendar=gregorian", "de-u-ca-gregory-co-phonebk"},
            {"th@numbers=thai;z=extz;x=priv-use;a=exta", "th-a-exta-u-nu-thai-z-extz-x-priv-use"},
            {"en@timezone=America/New_York;calendar=japanese", "en-u-ca-japanese-tz-usnyc"},
            {"en@timezone=US/Eastern", "en-u-tz-usnyc"},
            {"en@x=x-y-z;a=a-b-c", "en-x-x-y-z"},
            {
                "it@collation=badcollationtype;colStrength=identical;cu=usd-eur",
                "it-u-cu-usd-eur-ks-identic"
            },
            {"en_US_POSIX", "en-US-u-va-posix"},
            {"en_US_POSIX@calendar=japanese;currency=EUR", "en-US-u-ca-japanese-cu-eur-va-posix"},
            {"@x=elmer", "und-x-elmer"},
            {"_US@x=elmer", "und-US-x-elmer"},
            /* #12671 */
            {"en@a=bar;attribute=baz", "en-a-bar-u-baz"},
            {"en@a=bar;attribute=baz;x=u-foo", "en-a-bar-u-baz-x-u-foo"},
            {"en@attribute=baz", "en-u-baz"},
            {"en@attribute=baz;calendar=islamic-civil", "en-u-baz-ca-islamic-civil"},
            {"en@a=bar;calendar=islamic-civil;x=u-foo", "en-a-bar-u-ca-islamic-civil-x-u-foo"},
            {
                "en@a=bar;attribute=baz;calendar=islamic-civil;x=u-foo",
                "en-a-bar-u-baz-ca-islamic-civil-x-u-foo"
            },
            /* ICU-20320*/
            {"en@9=efg;a=baz", "en-9-efg-a-baz"},
            /* ICU-20478 */
            {"sl__ROZAJ_BISKE_1994", "sl-1994-biske-rozaj"},
            {"en__SCOUSE_FONIPA", "en-fonipa-scouse"},
            /* ICU-20310 */
            {"en-u-kn-true", "en-u-kn"},
            {"en-u-kn", "en-u-kn"},
            {"de-u-co-yes", "de-u-co"},
            {"de-u-co", "de-u-co"},
            {"de@collation=yes", "de-u-co"},
            {"cmn-hans-cn-u-ca-t-ca-x-t-u", "cmn-Hans-CN-t-ca-u-ca-x-t-u"},
            /* ICU-21414 */
            {"und-CN", "und-CN"},
            {"und-Latn", "und-Latn"},
            {"und-u-ca-roc", "und-u-ca-roc"},
            {"und-x-abc-private", "und-x-abc-private"},
            {"und-x-private", "und-x-private"},
            {"und-u-ca-roc-x-private", "und-u-ca-roc-x-private"},
            {"und-US-x-private", "und-US-x-private"},
            {"und-Latn-x-private", "und-Latn-x-private"},
            {"und-1994-biske-rozaj", "und-1994-biske-rozaj"},
            {"und-1994-biske-rozaj-x-private", "und-1994-biske-rozaj-x-private"},
        };

        for (int i = 0; i < locale_to_langtag.length; i++) {
            ULocale loc = new ULocale(locale_to_langtag[i][0]);
            String langtag = loc.toLanguageTag();
            if (!langtag.equals(locale_to_langtag[i][1])) {
                errln(
                        "FAIL: toLanguageTag returned language tag ["
                                + langtag
                                + "] for locale ["
                                + loc
                                + "] - expected: ["
                                + locale_to_langtag[i][1]
                                + "]");
            }
        }
    }

    @Test
    public void TestForLanguageTagBug20148() {
        ULocale uloc = ULocale.forLanguageTag("de-DE-1901-1901");
        assertEquals("ULocale.forLanguageTag(\"de-DE-1901-1901\") ", "de_DE_1901", uloc.getName());

        uloc = ULocale.forLanguageTag("de-DE-1aBc-1AbC");
        assertEquals("ULocale.forLanguageTag(\"de-DE-1aBc-1AbC\") ", "de_DE_1ABC", uloc.getName());

        uloc = ULocale.forLanguageTag("en-a-bbb-a-ccc");
        assertEquals("ULocale.forLanguageTag(\"en-a-bbb-a-ccc\") ", "en@a=bbb", uloc.getName());

        uloc = ULocale.forLanguageTag("en-A-bbb-a-ccc");
        assertEquals("ULocale.forLanguageTag(\"en-A-bbb-a-ccc\") ", "en@a=bbb", uloc.getName());
    }

    @Test
    public void TestForLanguageTagBug13776() {
        final Locale backupDefault = Locale.getDefault();
        try {
            Locale loc = Locale.forLanguageTag("ar-EG-u-nu-latn");
            Locale.setDefault(loc);
            ULocale uloc = ULocale.forLocale(loc);
            assertEquals("getKeywordValue(\"numbers\")", "latn", uloc.getKeywordValue("numbers"));
        } finally {
            Locale.setDefault(backupDefault);
        }
    }

    @Test
    public void TestDigitSingletonExtensionBug20320() {
        ULocale uloc = ULocale.forLanguageTag("en-0-abc-a-xyz");
        assertEquals("getExtension(\'a\')", "xyz", uloc.getExtension('a'));
        assertEquals("getExtension(\'0\')", "abc", uloc.getExtension('0'));
    }

    @Test
    public void TestConstructorAcceptsBCP47() {
        ULocale loc1 = new ULocale("ar-EG-u-nu-latn");
        ULocale loc2 = new ULocale("ar-EG@numbers=latn");
        ULocale loc3 = new ULocale("ar-EG");
        String val;

        // Check getKeywordValue "numbers"
        val = loc1.getKeywordValue("numbers");
        assertEquals("BCP47 syntax has ICU keyword value", "latn", val);

        val = loc2.getKeywordValue("numbers");
        assertEquals("ICU syntax has ICU keyword value", "latn", val);

        val = loc3.getKeywordValue("numbers");
        assertEquals("Default, ICU keyword", null, val);

        // Note: ICU does not have getUnicodeKeywordValue()
    }

    @Test
    public void TestForLanguageTag() {
        final Integer NOERROR = -1;

        final Object[][] langtag_to_locale = {
            {"en", "en", NOERROR},
            {"en-us", "en_US", NOERROR},
            {"und-us", "_US", NOERROR},
            {"und-latn", "_Latn", NOERROR},
            {"en-us-posix", "en_US_POSIX", NOERROR},
            {"de-de_euro", "de", 3},
            {"kok-in", "kok_IN", NOERROR},
            {"123", "", 0},
            {"en_us", "", 0},
            {"en-latn-x", "en_Latn", 8},
            {"art-lojban", "jbo", NOERROR},
            {"zh-hakka", "hak", NOERROR},
            {"zh-cmn-CH", "cmn_CH", NOERROR},
            {"xxx-yy", "xxx_YY", NOERROR},
            {"fr-234", "fr_234", NOERROR},
            {"i-default", "en@x=i-default", NOERROR},
            {"i-test", "", 0},
            {"ja-jp-jp", "ja_JP", 6},
            {"bogus", "bogus", NOERROR},
            {"boguslang", "", 0},
            {"EN-lATN-us", "en_Latn_US", NOERROR},
            {"und-variant-1234", "__1234_VARIANT", NOERROR}, /* ICU-20478 */
            {"und-varzero-var1-vartwo", "__VARZERO", 12},
            {"en-u-ca-gregory", "en@calendar=gregorian", NOERROR},
            {"en-U-cu-USD", "en@currency=usd", NOERROR},
            {"en-us-u-va-posix", "en_US_POSIX", NOERROR},
            {"en-us-u-ca-gregory-va-posix", "en_US_POSIX@calendar=gregorian", NOERROR},
            {"en-us-posix-u-va-posix", "en_US_POSIX@va=posix", NOERROR},
            {"en-us-u-va-posix2", "en_US@va=posix2", NOERROR},
            {"en-us-vari1-u-va-posix", "en_US_VARI1@va=posix", NOERROR},
            {"ar-x-1-2-3", "ar@x=1-2-3", NOERROR},
            {"fr-u-nu-latn-cu-eur", "fr@currency=eur;numbers=latn", NOERROR},
            {
                "de-k-kext-u-co-phonebk-nu-latn",
                "de@collation=phonebook;k=kext;numbers=latn",
                NOERROR
            },
            {"ja-u-cu-jpy-ca-jp", "ja@calendar=yes;currency=jpy;jp=yes", NOERROR},
            {"en-us-u-tz-usnyc", "en_US@timezone=America/New_York", NOERROR},
            {"und-a-abc-def", "@a=abc-def", NOERROR},
            {"zh-u-ca-chinese-x-u-ca-chinese", "zh@calendar=chinese;x=u-ca-chinese", NOERROR},
            {"fr--FR", "fr", 3},
            {"fr-", "fr", 3},
            {"x-elmer", "@x=elmer", NOERROR},
            {
                "en-US-u-attr1-attr2-ca-gregory",
                "en_US@attribute=attr1-attr2;calendar=gregorian",
                NOERROR
            },
            {"sr-u-kn", "sr@colnumeric=yes", NOERROR},
            {"de-u-kn-co-phonebk", "de@collation=phonebook;colnumeric=yes", NOERROR},
            {
                "en-u-attr2-attr1-kn-kb",
                "en@attribute=attr1-attr2;colbackwards=yes;colnumeric=yes",
                NOERROR
            },
            {
                "ja-u-ijkl-efgh-abcd-ca-japanese-xx-yyy-zzz-kn",
                "ja@attribute=abcd-efgh-ijkl;calendar=japanese;colnumeric=yes;xx=yyy-zzz",
                NOERROR
            },
            {
                "de-u-xc-xphonebk-co-phonebk-ca-buddhist-mo-very-lo-extensi-xd-that-de-should-vc-probably-xz-killthebuffer",
                "de@calendar=buddhist;collation=phonebook;de=should;lo=extensi;mo=very;vc=probably;xc=xphonebk;xd=that;xz=yes",
                92
            },
            /* #12761 */
            {"en-a-bar-u-baz", "en@a=bar;attribute=baz", NOERROR},
            {"en-a-bar-u-baz-x-u-foo", "en@a=bar;attribute=baz;x=u-foo", NOERROR},
            {"en-u-baz", "en@attribute=baz", NOERROR},
            {"en-u-baz-ca-islamic-civil", "en@attribute=baz;calendar=islamic-civil", NOERROR},
            {
                "en-a-bar-u-ca-islamic-civil-x-u-foo",
                "en@a=bar;calendar=islamic-civil;x=u-foo",
                NOERROR
            },
            {
                "en-a-bar-u-baz-ca-islamic-civil-x-u-foo",
                "en@a=bar;attribute=baz;calendar=islamic-civil;x=u-foo",
                NOERROR
            },
            /* #20098 */
            {"hant-cmn-cn", "hant", 5},
            {"zh-cmn-TW", "cmn_TW", NOERROR},
            {"zh-x_t-ab", "zh", 3},
            {"zh-hans-cn-u-ca-x_t-u", "zh_Hans_CN@calendar=yes", 16},
            /* #20140 dupe keys in U-extension */
            {"zh-u-ca-chinese-ca-gregory", "zh@calendar=chinese", NOERROR},
            {
                "zh-u-ca-gregory-co-pinyin-ca-chinese",
                "zh@calendar=gregorian;collation=pinyin",
                NOERROR
            },
            {
                "de-latn-DE-1901-u-co-phonebk-co-pinyin-ca-gregory",
                "de_Latn_DE_1901@calendar=gregorian;collation=phonebook",
                NOERROR
            },
            {"th-u-kf-nu-thai-kf-false", "th@colcasefirst=yes;numbers=thai", NOERROR},
            /* #20410 */
            {"art-lojban-x-0", "jbo@x=0", NOERROR},
            {"zh-xiang-u-nu-thai-x-0", "hsn@numbers=thai;x=0", NOERROR},
            /* ICU-20478 */
            {"ja-9876-5432", "ja__5432_9876", NOERROR},
            {"en-US-variantb-varianta", "en_US_VARIANTA_VARIANTB", NOERROR},
            {"en-US-varianta-variantb", "en_US_VARIANTA_VARIANTB", NOERROR},
            {"sl-rozaj-biske-1994", "sl__1994_BISKE_ROZAJ", NOERROR},
            {"sl-biske-rozaj-1994", "sl__1994_BISKE_ROZAJ", NOERROR},
            {"sl-biske-1994-rozaj", "sl__1994_BISKE_ROZAJ", NOERROR},
            {"sl-1994-biske-rozaj", "sl__1994_BISKE_ROZAJ", NOERROR},
            {"en-fonipa-scouse", "en__FONIPA_SCOUSE", NOERROR},
            {"en-scouse-fonipa", "en__FONIPA_SCOUSE", NOERROR},
            /* ICU-21433 */
            {"und-1994-biske-rozaj", "__1994_BISKE_ROZAJ", NOERROR},
            {"de-1994-biske-rozaj", "de__1994_BISKE_ROZAJ", NOERROR},
            {"und-x-private", "@x=private", NOERROR},
            {"de-1994-biske-rozaj-x-private", "de__1994_BISKE_ROZAJ@x=private", NOERROR},
            {"und-1994-biske-rozaj-x-private", "__1994_BISKE_ROZAJ@x=private", NOERROR},
        };

        for (int i = 0; i < langtag_to_locale.length; i++) {
            String tag = (String) langtag_to_locale[i][0];
            ULocale expected = new ULocale((String) langtag_to_locale[i][1]);
            ULocale loc = ULocale.forLanguageTag(tag);

            if (!loc.equals(expected)) {
                errln(
                        "FAIL: forLanguageTag returned locale ["
                                + loc
                                + "] for language tag ["
                                + tag
                                + "] - expected: ["
                                + expected
                                + "]");
            }
        }

        // Use locale builder to check errors
        for (int i = 0; i < langtag_to_locale.length; i++) {
            String tag = (String) langtag_to_locale[i][0];
            ULocale expected = new ULocale((String) langtag_to_locale[i][1]);
            int errorIdx = ((Integer) langtag_to_locale[i][2]).intValue();

            try {
                Builder bld = new Builder();
                bld.setLanguageTag(tag);
                ULocale loc = bld.build();

                if (!loc.equals(expected)) {
                    errln(
                            "FAIL: forLanguageTag returned locale ["
                                    + loc
                                    + "] for language tag ["
                                    + tag
                                    + "] - expected: ["
                                    + expected
                                    + "]");
                }
                if (errorIdx != NOERROR.intValue()) {
                    errln(
                            "FAIL: Builder.setLanguageTag should throw an exception for input tag ["
                                    + tag
                                    + "]");
                }
            } catch (IllformedLocaleException ifle) {
                if (ifle.getErrorIndex() != errorIdx) {
                    errln(
                            "FAIL: Builder.setLanguageTag returned error index "
                                    + ifle.getErrorIndex()
                                    + " for input language tag ["
                                    + tag
                                    + "] expected: "
                                    + errorIdx);
                }
            }
        }
    }

    /*
     * Test that if you use any locale without keyword that you will get a NULL
     * string returned and not throw and exception.
     */
    @Test
    public void Test4735() {
        try {
            new ULocale("und").getDisplayKeywordValue("calendar", ULocale.GERMAN);
            new ULocale("en").getDisplayKeywordValue("calendar", ULocale.GERMAN);
        } catch (Exception e) {
            errln("Unexpected exception: " + e.getMessage());
        }
    }

    @Test
    public void TestGetFallback() {
        // Testing static String getFallback(String)
        final String[][] TESTIDS = {
            {"en_US", "en", "", ""}, // ULocale.getFallback("") should return ""
            {"EN_us_Var", "en_US", "en", ""}, // Case is always normalized
            {
                "de_DE@collation=phonebook",
                "de@collation=phonebook",
                "@collation=phonebook",
                "@collation=phonebook"
            }, // Keyword is preserved
            {"en__POSIX", "en", ""}, // Trailing empty segment should be truncated
            {"_US_POSIX", "_US", ""}, // Same as above
            {"root", ""}, // No canonicalization
        };

        for (String[] chain : TESTIDS) {
            for (int i = 1; i < chain.length; i++) {
                String fallback = ULocale.getFallback(chain[i - 1]);
                assertEquals("getFallback(\"" + chain[i - 1] + "\")", chain[i], fallback);
            }
        }

        // Testing ULocale getFallback()
        final ULocale[][] TESTLOCALES = {
            {new ULocale("en_US"), new ULocale("en"), ULocale.ROOT, null},
            {new ULocale("en__POSIX"), new ULocale("en"), ULocale.ROOT, null},
            {
                new ULocale("de_DE@collation=phonebook"),
                new ULocale("de@collation=phonebook"),
                new ULocale("@collation=phonebook"),
                null
            },
            {new ULocale("_US_POSIX"), new ULocale("_US"), ULocale.ROOT, null},
            {new ULocale("root"), null},
        };

        for (ULocale[] chain : TESTLOCALES) {
            for (int i = 1; i < chain.length; i++) {
                ULocale fallback = chain[i - 1].getFallback();
                assertEquals("ULocale(" + chain[i - 1] + ").getFallback()", chain[i], fallback);
            }
        }
    }

    @Test
    public void TestExtension() {
        String[][] TESTCASES = {
            // {"<langtag>", "<ext key1>", "<ext val1>", "<ext key2>", "<ext val2>", ....},
            {"en"},
            {"en-a-exta-b-extb", "a", "exta", "b", "extb"},
            {"en-b-extb-a-exta", "a", "exta", "b", "extb"},
            {"de-x-a-bc-def", "x", "a-bc-def"},
            {"ja-JP-u-cu-jpy-ca-japanese-x-java", "u", "ca-japanese-cu-jpy", "x", "java"},
        };

        for (String[] testcase : TESTCASES) {
            ULocale loc = ULocale.forLanguageTag(testcase[0]);

            int nExtensions = (testcase.length - 1) / 2;

            Set<Character> keys = loc.getExtensionKeys();
            if (keys.size() != nExtensions) {
                errln(
                        "Incorrect number of extensions: returned="
                                + keys.size()
                                + ", expected="
                                + nExtensions
                                + ", locale="
                                + testcase[0]);
            }

            for (int i = 0; i < nExtensions; i++) {
                String kstr = testcase[i / 2 + 1];
                String ext = loc.getExtension(kstr.charAt(0));
                if (ext == null || !ext.equals(testcase[i / 2 + 2])) {
                    errln(
                            "Incorrect extension value: key="
                                    + kstr
                                    + ", returned="
                                    + ext
                                    + ", expected="
                                    + testcase[i / 2 + 2]
                                    + ", locale="
                                    + testcase[0]);
                }
            }
        }

        // Exception handling
        boolean sawException = false;
        try {
            ULocale l = ULocale.forLanguageTag("en-US-a-exta");
            l.getExtension('$');
        } catch (IllegalArgumentException e) {
            sawException = true;
        }
        if (!sawException) {
            errln("getExtension must throw an exception on illegal input key");
        }
    }

    @Test
    public void TestUnicodeLocaleExtension() {
        String[][] TESTCASES = {
            // "<langtag>", "<attr1>,<attr2>,...", "<key1>,<key2>,...", "<type1>", "<type2>", ...},
            {"en", null, null},
            {"en-a-ext1-x-privuse", null, null},
            {"en-a-ext1-u-ca-roc-x-privuse", null, "ca", "roc", null},
            {"en-u-attr1-attr2", "attr1,attr2", null},
            {"ja-u-ca-japanese-cu-jpy", null, "ca,cu", "japanese", "jpy"},
            {"th-TH-u-number-attr-nu-thai-ca-buddhist", "attr,number", "ca,nu", "buddhist", "thai"},
        };

        for (String[] testcase : TESTCASES) {
            ULocale loc = ULocale.forLanguageTag(testcase[0]);

            Set<String> expectedAttributes = new HashSet<>();
            if (testcase[1] != null) {
                String[] attrs = testcase[1].split(",");
                for (String s : attrs) {
                    expectedAttributes.add(s);
                }
            }

            Map<String, String> expectedKeywords = new HashMap<>();
            if (testcase[2] != null) {
                String[] ukeys = testcase[2].split(",");
                for (int i = 0; i < ukeys.length; i++) {
                    expectedKeywords.put(ukeys[i], testcase[i + 3]);
                }
            }

            // Check attributes
            Set<String> attributes = loc.getUnicodeLocaleAttributes();
            if (attributes.size() != expectedAttributes.size()) {
                errln(
                        "Incorrect number for Unicode locale attributes: returned="
                                + attributes.size()
                                + ", expected="
                                + expectedAttributes.size()
                                + ", locale="
                                + testcase[0]);
            }
            if (!attributes.containsAll(expectedAttributes)
                    || !expectedAttributes.containsAll(attributes)) {
                errln("Incorrect set of attributes for locale " + testcase[0]);
            }

            // Check keywords
            Set<String> keys = loc.getUnicodeLocaleKeys();
            Set<String> expectedKeys = expectedKeywords.keySet();
            if (keys.size() != expectedKeys.size()) {
                errln(
                        "Incorrect number for Unicode locale keys: returned="
                                + keys.size()
                                + ", expected="
                                + expectedKeys.size()
                                + ", locale="
                                + testcase[0]);
            }

            for (String expKey : expectedKeys) {
                String type = loc.getUnicodeLocaleType(expKey);
                String expType = expectedKeywords.get(expKey);

                if (type == null || !expType.equals(type)) {
                    errln(
                            "Incorrect Unicode locale type: key="
                                    + expKey
                                    + ", returned="
                                    + type
                                    + ", expected="
                                    + expType
                                    + ", locale="
                                    + testcase[0]);
                }
            }
        }

        // Exception handling
        boolean sawException = false;
        try {
            ULocale l = ULocale.forLanguageTag("en-US-u-ca-gregory");
            l.getUnicodeLocaleType("$%");
        } catch (IllegalArgumentException e) {
            sawException = true;
        }
        if (!sawException) {
            errln("getUnicodeLocaleType must throw an exception on illegal input key");
        }
    }

    @Test
    public void TestForLocale() {
        Object[][] DATA = {
            {new Locale(""), ""},
            {new Locale("en", "US"), "en_US"},
            {new Locale("en", "US", "POSIX"), "en_US_POSIX"},
            {new Locale("", "US"), "_US"},
            {new Locale("en", "", "POSIX"), "en__POSIX"},
            {new Locale("no", "NO", "NY"), "nn_NO"},
            {
                new Locale("en", "BOGUS"), "en__BOGUS"
            }, // ill-formed country is mapped to variant - see #8383 and #8384
        };

        for (int i = 0; i < DATA.length; i++) {
            ULocale uloc = ULocale.forLocale((Locale) DATA[i][0]);
            assertEquals("forLocale with " + DATA[i][0], DATA[i][1], uloc.getName());
        }

        if (JAVA7_OR_LATER) {
            Object[][] DATA7 = {
                {new Locale("ja", "JP", "JP"), "ja_JP_JP@calendar=japanese"},
                {new Locale("th", "TH", "TH"), "th_TH_TH@numbers=thai"},
            };
            for (int i = 0; i < DATA7.length; i++) {
                ULocale uloc = ULocale.forLocale((Locale) DATA7[i][0]);
                assertEquals("forLocale with " + DATA7[i][0], DATA7[i][1], uloc.getName());
            }

            try {
                Method localeForLanguageTag =
                        Locale.class.getMethod("forLanguageTag", String.class);

                String[][] DATA7EXT = {
                    {"en-Latn-US", "en_Latn_US"},
                    {"zh-Hant-TW", "zh_Hant_TW"},
                    {"und-US-u-cu-usd", "_US@currency=usd"},
                    {"th-TH-u-ca-buddhist-nu-thai", "th_TH@calendar=buddhist;numbers=thai"},
                    {"en-US-u-va-POSIX", "en_US_POSIX"},
                    {"de-DE-u-co-phonebk", "de_DE@collation=phonebook"},
                    {"en-a-exta-b-extb-x-privu", "en@a=exta;b=extb;x=privu"},
                    {"fr-u-attr1-attr2-cu-eur", "fr@attribute=attr1-attr2;currency=eur"},
                };

                for (int i = 0; i < DATA7EXT.length; i++) {
                    Locale loc = (Locale) localeForLanguageTag.invoke(null, DATA7EXT[i][0]);
                    ULocale uloc = ULocale.forLocale(loc);
                    assertEquals("forLocale with " + loc, DATA7EXT[i][1], uloc.getName());
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }

        } else {
            Object[][] DATA6 = {
                {new Locale("ja", "JP", "JP"), "ja_JP@calendar=japanese"},
                {new Locale("th", "TH", "TH"), "th_TH@numbers=thai"},
            };
            for (int i = 0; i < DATA6.length; i++) {
                ULocale uloc = ULocale.forLocale((Locale) DATA6[i][0]);
                assertEquals("forLocale with " + DATA6[i][0], DATA6[i][1], uloc.getName());
            }
        }
    }

    @Test
    public void TestToLocale() {
        Object[][] DATA = {
            {"", new Locale("")},
            {"en_US", new Locale("en", "US")},
            {"_US", new Locale("", "US")},
            {"en__POSIX", new Locale("en", "", "POSIX")},
        };

        for (int i = 0; i < DATA.length; i++) {
            Locale loc = new ULocale((String) DATA[i][0]).toLocale();
            assertEquals("toLocale with " + DATA[i][0], DATA[i][1], loc);
        }

        if (JAVA7_OR_LATER) {
            Object[][] DATA7 = {
                {"nn_NO", new Locale("nn", "NO")},
                {"no_NO_NY", new Locale("no", "NO", "NY")},
            };
            for (int i = 0; i < DATA7.length; i++) {
                Locale loc = new ULocale((String) DATA7[i][0]).toLocale();
                assertEquals("toLocale with " + DATA7[i][0], DATA7[i][1], loc);
            }

            try {
                Method localeForLanguageTag =
                        Locale.class.getMethod("forLanguageTag", String.class);

                String[][] DATA7EXT = {
                    {"en_Latn_US", "en-Latn-US"},
                    {"zh_Hant_TW", "zh-Hant-TW"},
                    {"ja_JP@calendar=japanese", "ja-JP-u-ca-japanese"},
                    {"ja_JP_JP@calendar=japanese", "ja-JP-u-ca-japanese-x-lvariant-JP"},
                    {"th_TH@numbers=thai", "th-TH-u-nu-thai"},
                    {"th_TH_TH@numbers=thai", "th-TH-u-nu-thai-x-lvariant-TH"},
                    {"de@collation=phonebook", "de-u-co-phonebk"},
                    {"en@a=exta;b=extb;x=privu", "en-a-exta-b-extb-x-privu"},
                    {"fr@attribute=attr1-attr2;currency=eur", "fr-u-attr1-attr2-cu-eur"},
                };

                for (int i = 0; i < DATA7EXT.length; i++) {
                    Locale loc = new ULocale(DATA7EXT[i][0]).toLocale();
                    Locale expected = (Locale) localeForLanguageTag.invoke(null, DATA7EXT[i][1]);
                    assertEquals("toLocale with " + DATA7EXT[i][0], expected, loc);
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }

        } else {
            Object[][] DATA6 = {
                {"nn_NO", new Locale("no", "NO", "NY")},
                {"no_NO_NY", new Locale("no", "NO", "NY")},
                {"ja_JP@calendar=japanese", new Locale("ja", "JP", "JP")},
                {"th_TH@numbers=thai", new Locale("th", "TH", "TH")},
            };
            for (int i = 0; i < DATA6.length; i++) {
                Locale loc = new ULocale((String) DATA6[i][0]).toLocale();
                assertEquals("toLocale with " + DATA6[i][0], DATA6[i][1], loc);
            }
        }
    }

    @Test
    public void TestCategoryDefault() {
        Locale backupDefault = Locale.getDefault();

        ULocale orgDefault = ULocale.getDefault();

        // Setting a category default won't change default ULocale
        ULocale uJaJp = new ULocale("ja_JP");
        ULocale uDeDePhonebook = new ULocale("de_DE@collation=phonebook");

        ULocale.setDefault(Category.DISPLAY, uJaJp);
        ULocale.setDefault(Category.FORMAT, uDeDePhonebook);

        if (!ULocale.getDefault().equals(orgDefault)) {
            errln("FAIL: Default ULocale is " + ULocale.getDefault() + ", expected: " + orgDefault);
        }

        if (!ULocale.getDefault(Category.DISPLAY).equals(uJaJp)) {
            errln(
                    "FAIL: DISPLAY ULocale is "
                            + ULocale.getDefault(Category.DISPLAY)
                            + ", expected: "
                            + uJaJp);
        }

        if (!ULocale.getDefault(Category.FORMAT).equals(uDeDePhonebook)) {
            errln(
                    "FAIL: FORMAT ULocale is "
                            + ULocale.getDefault(Category.FORMAT)
                            + ", expected: "
                            + uDeDePhonebook);
        }

        // Setting ULocale default will overrides category defaults
        ULocale uFrFr = new ULocale("fr_FR");

        ULocale.setDefault(uFrFr);

        if (!ULocale.getDefault(Category.DISPLAY).equals(uFrFr)) {
            errln(
                    "FAIL: DISPLAY ULocale is "
                            + ULocale.getDefault(Category.DISPLAY)
                            + ", expected: "
                            + uFrFr);
        }

        if (!ULocale.getDefault(Category.FORMAT).equals(uFrFr)) {
            errln(
                    "FAIL: FORMAT ULocale is "
                            + ULocale.getDefault(Category.FORMAT)
                            + ", expected: "
                            + uFrFr);
        }

        // Setting Locale default will updates ULocale default and category defaults
        Locale arEg = new Locale("ar", "EG");
        ULocale uArEg = ULocale.forLocale(arEg);

        Locale.setDefault(arEg);

        if (!ULocale.getDefault().equals(uArEg)) {
            errln("FAIL: Default ULocale is " + ULocale.getDefault() + ", expected: " + uArEg);
        }

        if (!ULocale.getDefault(Category.DISPLAY).equals(uArEg)) {
            errln(
                    "FAIL: DISPLAY ULocale is "
                            + ULocale.getDefault(Category.DISPLAY)
                            + ", expected: "
                            + uArEg);
        }

        if (!ULocale.getDefault(Category.FORMAT).equals(uArEg)) {
            errln(
                    "FAIL: FORMAT ULocale is "
                            + ULocale.getDefault(Category.FORMAT)
                            + ", expected: "
                            + uArEg);
        }

        // Restore back up
        Locale.setDefault(backupDefault);
    }

    //
    // Test case for the behavior of Comparable implementation.
    //
    @Test
    public void TestComparable() {
        // Test strings used for creating ULocale objects.
        // This list contains multiple different strings creating
        // multiple equivalent locales.
        final String[] localeStrings = {
            "en",
            "EN",
            "en_US",
            "en_GB",
            "en_US_POSIX",
            "en_us_posix",
            "ar_EG",
            "zh_Hans_CN",
            "zh_Hant_TW",
            "zh_Hans",
            "zh_CN",
            "zh_TW",
            "th_TH@calendar=buddhist;numbers=thai",
            "TH_TH@NUMBERS=thai;CALENDAR=buddhist",
            "th_TH@calendar=buddhist",
            "th_TH@calendar=gergorian",
            "th_TH@numbers=latn",
            "abc_def_ghi_jkl_opq",
            "abc_DEF_ghi_JKL_opq",
            "",
            "und",
            "This is a bogus locale ID",
            "This is a BOGUS locale ID",
            "en_POSIX",
            "en__POSIX",
        };

        ULocale[] locales = new ULocale[localeStrings.length];
        for (int i = 0; i < locales.length; i++) {
            locales[i] = new ULocale(localeStrings[i]);
        }

        // compares all permutations
        for (int i = 0; i < locales.length; i++) {
            for (int j = i /* including the locale itself */; j < locales.length; j++) {
                boolean eqls1 = locales[i].equals(locales[j]);
                boolean eqls2 = locales[i].equals(locales[j]);

                if (eqls1 != eqls2) {
                    errln(
                            "FAILED: loc1.equals(loc2) and loc2.equals(loc1) return different results: loc1="
                                    + locales[i]
                                    + ", loc2="
                                    + locales[j]);
                }

                int cmp1 = locales[i].compareTo(locales[j]);
                int cmp2 = locales[j].compareTo(locales[i]);

                if ((cmp1 == 0) != eqls1) {
                    errln(
                            "FAILED: inconsistent equals and compareTo: loc1="
                                    + locales[i]
                                    + ", loc2="
                                    + locales[j]);
                }
                if (cmp1 < 0 && cmp2 <= 0 || cmp1 > 0 && cmp2 >= 0 || cmp1 == 0 && cmp2 != 0) {
                    errln(
                            "FAILED: loc1.compareTo(loc2) is inconsistent with loc2.compareTo(loc1): loc1="
                                    + locales[i]
                                    + ", loc2="
                                    + locales[j]);
                }
            }
        }

        // Make sure ULocale objects can be sorted by the Java collection
        // framework class without providing a Comparator, and equals/compareTo
        // are consistent.

        // The sorted locale list created from localeStrings above.
        // Duplicated locales are removed and locale string is normalized
        // (by the ULocale constructor).
        final String[] sortedLocaleStrings = {
            "",
            "abc_DEF_GHI_JKL_OPQ",
            "ar_EG",
            "en",
            "en__POSIX",
            "en_GB",
            "en_US",
            "en_US_POSIX",
            "th_TH@calendar=buddhist",
            "th_TH@calendar=buddhist;numbers=thai",
            "th_TH@calendar=gergorian",
            "th_TH@numbers=latn",
            "this is a bogus locale id",
            "zh_CN",
            "zh_TW",
            "zh_Hans",
            "zh_Hans_CN",
            "zh_Hant_TW",
        };

        TreeSet<ULocale> sortedLocales = new TreeSet<>();
        for (ULocale locale : locales) {
            sortedLocales.add(locale);
        }

        // Check the number of unique locales
        if (sortedLocales.size() != sortedLocaleStrings.length) {
            errln(
                    "FAILED: Number of unique locales: "
                            + sortedLocales.size()
                            + ", expected: "
                            + sortedLocaleStrings.length);
        }

        // Check the order
        int i = 0;
        for (ULocale loc : sortedLocales) {
            if (!loc.toString().equals(sortedLocaleStrings[i++])) {
                errln("FAILED: Sort order is incorrect for " + loc.toString());
                break;
            }
        }
    }

    @Test
    public void TestToUnicodeLocaleKey() {
        String[][] DATA = {
            {"calendar", "ca"},
            {"CALEndar", "ca"}, // difference casing
            {"ca", "ca"}, // bcp key itself
            {"kv", "kv"}, // no difference between legacy and bcp
            {"foo", null}, // unknown, bcp ill-formed
            {"ZZ", "zz"}, // unknown, bcp well-formed
        };

        for (String[] d : DATA) {
            String keyword = d[0];
            String expected = d[1];

            String bcpKey = ULocale.toUnicodeLocaleKey(keyword);
            assertEquals("keyword=" + keyword, expected, bcpKey);
        }
    }

    @Test
    public void TestBug20321UnicodeLocaleKey() {
        // key = alphanum alpha ;
        String[] INVALID = {
            "a0", "00", "a@", "0@", "@a", "@a", "abc", "0bc",
        };

        for (String invalid : INVALID) {
            String bcpKey = ULocale.toUnicodeLocaleKey(invalid);
            assertNull("keyword=" + invalid, bcpKey);
        }

        String[] VALID = {
            "aa", "0a",
        };

        for (String valid : VALID) {
            String bcpKey = ULocale.toUnicodeLocaleKey(valid);
            assertEquals("keyword=" + valid, valid, bcpKey);
        }
        ;
    }

    @Test
    public void TestToLegacyKey() {
        String[][] DATA = {
            {"kb", "colbackwards"},
            {"kB", "colbackwards"}, // different casing
            {"Collation", "collation"}, // keyword itself with different casing
            {"kv", "kv"}, // no difference between legacy and bcp
            {"foo", "foo"}, // unknown, bcp ill-formed
            {"ZZ", "zz"}, // unknown, bcp well-formed
            {"e=mc2", null}, // unknown, bcp/legacy ill-formed
        };

        for (String[] d : DATA) {
            String keyword = d[0];
            String expected = d[1];

            String legacyKey = ULocale.toLegacyKey(keyword);
            assertEquals("bcpKey=" + keyword, expected, legacyKey);
        }
    }

    @Test
    public void TestToUnicodeLocaleType() {
        String[][] DATA = {
            {"tz", "Asia/Kolkata", "inccu"},
            {"calendar", "gregorian", "gregory"},
            {"ca", "gregorian", "gregory"},
            {"ca", "Gregorian", "gregory"},
            {"ca", "buddhist", "buddhist"},
            {"Calendar", "Japanese", "japanese"},
            {"calendar", "Islamic-Civil", "islamic-civil"},
            {"calendar", "islamicc", "islamic-civil"}, // bcp type alias
            {"colalternate", "NON-IGNORABLE", "noignore"},
            {"colcaselevel", "yes", "true"},
            {"rg", "GBzzzz", "gbzzzz"},
            {"tz", "america/new_york", "usnyc"},
            {"tz", "Asia/Kolkata", "inccu"},
            {"timezone", "navajo", "usden"},
            {"ca", "aaaa", "aaaa"}, // unknown type, well-formed type
            {
                "ca", "gregory-japanese-islamic", "gregory-japanese-islamic"
            }, // unknown type, well-formed type
            {"zz", "gregorian", null}, // unknown key, ill-formed type
            {"co", "foo-", null}, // unknown type, ill-formed type
            {"variableTop", "00A0", "00a0"}, // valid codepoints type
            {"variableTop", "wxyz", "wxyz"}, // invalid codepoints type - return as is for now
            {"kr", "space-punct", "space-punct"}, // valid reordercode type
            {"kr", "digit-spacepunct", null}, // invalid reordercode type
        };

        for (String[] d : DATA) {
            String keyword = d[0];
            String value = d[1];
            String expected = d[2];

            String bcpType = ULocale.toUnicodeLocaleType(keyword, value);
            assertEquals("keyword=" + keyword + ", value=" + value, expected, bcpType);
        }
    }

    @Test
    public void TestToLegacyType() {
        String[][] DATA = {
            {"calendar", "gregory", "gregorian"},
            {"ca", "gregory", "gregorian"},
            {"ca", "Gregory", "gregorian"},
            {"ca", "buddhist", "buddhist"},
            {"Calendar", "Japanese", "japanese"},
            {"calendar", "Islamic-Civil", "islamic-civil"},
            {"calendar", "islamicc", "islamic-civil"}, // bcp type alias
            {"colalternate", "noignore", "non-ignorable"},
            {"colcaselevel", "true", "yes"},
            {"rg", "gbzzzz", "gbzzzz"},
            {"tz", "usnyc", "America/New_York"},
            {"tz", "inccu", "Asia/Calcutta"},
            {"timezone", "usden", "America/Denver"},
            {"timezone", "usnavajo", "America/Denver"}, // bcp type alias
            {"colstrength", "quarternary", "quaternary"}, // type alias
            {"ca", "aaaa", "aaaa"}, // unknown type
            {
                "calendar", "gregory-japanese-islamic", "gregory-japanese-islamic"
            }, // unknown type, well-formed type
            {"zz", "gregorian", "gregorian"}, // unknown key, bcp ill-formed type
            {"ca", "gregorian-calendar", "gregorian-calendar"}, // known key, bcp ill-formed type
            {"co", "e=mc2", null}, // known key, ill-formed bcp/legacy type
            {"variableTop", "00A0", "00a0"}, // valid codepoints type
            {"variableTop", "wxyz", "wxyz"}, // invalid codepoints type - return as is for now
            {"kr", "space-punct", "space-punct"}, // valid reordercode type
            {
                "kr", "digit-spacepunct", "digit-spacepunct"
            }, // invalid reordercode type, but ok for legacy syntax
        };

        for (String[] d : DATA) {
            String keyword = d[0];
            String value = d[1];
            String expected = d[2];

            String legacyType = ULocale.toLegacyType(keyword, value);
            assertEquals("keyword=" + keyword + ", value=" + value, expected, legacyType);
        }
    }

    @Test
    public void TestIsRightToLeft() {
        assertFalse("root LTR", ULocale.ROOT.isRightToLeft());
        assertFalse("zh LTR", ULocale.CHINESE.isRightToLeft());
        assertTrue("ar RTL", new ULocale("ar").isRightToLeft());
        assertTrue("und-EG RTL", new ULocale("und-EG").isRightToLeft());
        assertFalse("fa-Cyrl LTR", new ULocale("fa-Cyrl").isRightToLeft());
        assertTrue("en-Hebr RTL", new ULocale("en-Hebr").isRightToLeft());
        assertTrue("ckb RTL", new ULocale("ckb").isRightToLeft()); // Sorani Kurdish
        assertFalse("fil LTR", new ULocale("fil").isRightToLeft());
        assertFalse("he-Zyxw LTR", new ULocale("he-Zyxw").isRightToLeft());
    }

    @Test
    public void TestChineseToLocale() {
        final ULocale[][] LOCALES = {
            {ULocale.CHINESE, new ULocale("zh")},
            {ULocale.SIMPLIFIED_CHINESE, new ULocale("zh_Hans")},
            {ULocale.TRADITIONAL_CHINESE, new ULocale("zh_Hant")},
            {ULocale.CHINA, new ULocale("zh_Hans_CN")},
            {ULocale.PRC, new ULocale("zh_Hans_CN")},
            {ULocale.TAIWAN, new ULocale("zh_Hant_TW")},
        };

        // When two ULocales are equal, results of ULocale#toLocale() must be
        // also equal.
        for (ULocale[] pair : LOCALES) {
            if (pair[0].equals(pair[1])) {
                assertEquals(pair[0].toString(), pair[0].toLocale(), pair[1].toLocale());
            } else {
                // This could happen when the definition of ULocale constant is changed.
                // When it happens, it could be a mistake. So we use errln below.
                // If we change the definitioin for a legitimate reason, then the hardcoded
                // test data above should be reviewed and updated.
                errln("Error: " + pair[0] + " is not equal to " + pair[1]);
            }
        }
    }

    @Test
    public void TestUnd() {
        final String empty = "";
        final String root = "root";
        final String und = "und";

        ULocale empty_new = new ULocale(empty);
        ULocale empty_tag = ULocale.forLanguageTag(empty);

        ULocale root_new = new ULocale(root);
        ULocale root_tag = ULocale.forLanguageTag(root);
        ULocale root_build = new Builder().setLanguageTag(root).build();

        ULocale und_new = new ULocale(und);
        ULocale und_tag = ULocale.forLanguageTag(und);
        ULocale und_build = new Builder().setLanguageTag(und).build();

        Assert.assertEquals(empty, empty_new.getName());
        Assert.assertEquals(empty, root_new.getName());
        Assert.assertEquals(empty, und_new.getName());

        Assert.assertEquals(empty, empty_tag.getName());
        Assert.assertEquals(empty, root_tag.getName());
        Assert.assertEquals(empty, und_tag.getName());

        Assert.assertEquals(empty, root_build.getName());
        Assert.assertEquals(empty, und_build.getName());

        Assert.assertEquals(und, empty_new.toLanguageTag());
        Assert.assertEquals(und, root_new.toLanguageTag());
        Assert.assertEquals(und, und_new.toLanguageTag());

        Assert.assertEquals(und, empty_tag.toLanguageTag());
        Assert.assertEquals(und, root_tag.toLanguageTag());
        Assert.assertEquals(und, und_tag.toLanguageTag());

        Assert.assertEquals(und, root_build.toLanguageTag());
        Assert.assertEquals(und, und_build.toLanguageTag());

        Assert.assertEquals(empty_new, empty_tag);

        Assert.assertEquals(root_new, root_tag);
        Assert.assertEquals(root_new, root_build);
        Assert.assertEquals(root_tag, root_build);

        Assert.assertEquals(und_new, und_tag);
        Assert.assertEquals(und_new, und_build);
        Assert.assertEquals(und_tag, und_build);

        Assert.assertEquals(empty_new, root_new);
        Assert.assertEquals(empty_new, und_new);
        Assert.assertEquals(root_new, und_new);

        Assert.assertEquals(empty_tag, root_tag);
        Assert.assertEquals(empty_tag, und_tag);
        Assert.assertEquals(root_tag, und_tag);

        Assert.assertEquals(root_build, und_build);

        final ULocale displayLocale = ULocale.ENGLISH;
        final String displayName = "Unknown language";

        Assert.assertEquals(displayName, empty_new.getDisplayName(displayLocale));
        Assert.assertEquals(displayName, root_new.getDisplayName(displayLocale));
        Assert.assertEquals(displayName, und_new.getDisplayName(displayLocale));

        Assert.assertEquals(displayName, empty_tag.getDisplayName(displayLocale));
        Assert.assertEquals(displayName, root_tag.getDisplayName(displayLocale));
        Assert.assertEquals(displayName, und_tag.getDisplayName(displayLocale));

        Assert.assertEquals(displayName, root_build.getDisplayName(displayLocale));
        Assert.assertEquals(displayName, und_build.getDisplayName(displayLocale));
    }

    @Test
    public void TestUndScript() {
        final String id = "_Cyrl";
        final String tag = "und-Cyrl";
        final String script = "Cyrl";

        ULocale locale_new = new ULocale(id);
        ULocale locale_legacy = new ULocale(tag);
        ULocale locale_tag = ULocale.forLanguageTag(tag);
        ULocale locale_build = new Builder().setScript(script).build();

        Assert.assertEquals(id, locale_new.getName());
        Assert.assertEquals(id, locale_legacy.getName());
        Assert.assertEquals(id, locale_tag.getName());
        Assert.assertEquals(id, locale_build.getName());

        Assert.assertEquals(tag, locale_new.toLanguageTag());
        Assert.assertEquals(tag, locale_legacy.toLanguageTag());
        Assert.assertEquals(tag, locale_tag.toLanguageTag());
        Assert.assertEquals(tag, locale_build.toLanguageTag());

        Assert.assertEquals(locale_new, locale_legacy);
        Assert.assertEquals(locale_new, locale_tag);
        Assert.assertEquals(locale_new, locale_build);
        Assert.assertEquals(locale_tag, locale_build);

        final ULocale displayLocale = ULocale.ENGLISH;
        final String displayName = "Unknown language (Cyrillic)";

        Assert.assertEquals(displayName, locale_new.getDisplayName(displayLocale));
        Assert.assertEquals(displayName, locale_legacy.getDisplayName(displayLocale));
        Assert.assertEquals(displayName, locale_tag.getDisplayName(displayLocale));
        Assert.assertEquals(displayName, locale_build.getDisplayName(displayLocale));
    }

    @Test
    public void TestUndRegion() {
        final String id = "_AQ";
        final String tag = "und-AQ";
        final String region = "AQ";

        ULocale locale_new = new ULocale(id);
        ULocale locale_legacy = new ULocale(tag);
        ULocale locale_tag = ULocale.forLanguageTag(tag);
        ULocale locale_build = new Builder().setRegion(region).build();

        Assert.assertEquals(id, locale_new.getName());
        Assert.assertEquals(id, locale_legacy.getName());
        Assert.assertEquals(id, locale_tag.getName());
        Assert.assertEquals(id, locale_build.getName());

        Assert.assertEquals(tag, locale_new.toLanguageTag());
        Assert.assertEquals(tag, locale_legacy.toLanguageTag());
        Assert.assertEquals(tag, locale_tag.toLanguageTag());
        Assert.assertEquals(tag, locale_build.toLanguageTag());

        Assert.assertEquals(locale_new, locale_legacy);
        Assert.assertEquals(locale_new, locale_tag);
        Assert.assertEquals(locale_new, locale_build);
        Assert.assertEquals(locale_tag, locale_build);

        final ULocale displayLocale = ULocale.ENGLISH;
        final String displayName = "Unknown language (Antarctica)";

        Assert.assertEquals(displayName, locale_new.getDisplayName(displayLocale));
        Assert.assertEquals(displayName, locale_legacy.getDisplayName(displayLocale));
        Assert.assertEquals(displayName, locale_tag.getDisplayName(displayLocale));
        Assert.assertEquals(displayName, locale_build.getDisplayName(displayLocale));
    }

    @Test
    public void Test20900() {
        final String[][] testData =
                new String[][] {
                    {"art-lojban", "jbo"},
                    {"zh-guoyu", "zh"},
                    {"zh-hakka", "hak"},
                    {"zh-xiang", "hsn"},
                    {"zh-min-nan", "nan"},
                    {"zh-gan", "gan"},
                    {"zh-yue", "yue"},
                };
        for (int row = 0; row < testData.length; row++) {
            ULocale loc = ULocale.createCanonical(testData[row][0]);
            Assert.assertEquals(testData[row][1], loc.toLanguageTag());
        }
    }

    // Helper function
    private String canonicalTag(String languageTag) {
        return ULocale.createCanonical(ULocale.forLanguageTag(languageTag)).toLanguageTag();
    }

    @Test
    public void TestCanonical() {
        // Test replacement of languageAlias

        // language _ variant -> language
        Assert.assertEquals("nb", canonicalTag("no-BOKMAL"));
        // also test with script, country and extensions
        Assert.assertEquals(
                "nb-Cyrl-ID-u-ca-japanese", canonicalTag("no-Cyrl-ID-BOKMAL-u-ca-japanese"));
        // also test with other variants, script, country and extensions
        Assert.assertEquals(
                "nb-Cyrl-ID-1901-xsistemo-u-ca-japanese",
                canonicalTag("no-Cyrl-ID-1901-BOKMAL-xsistemo-u-ca-japanese"));
        Assert.assertEquals(
                "nb-Cyrl-ID-1901-u-ca-japanese",
                canonicalTag("no-Cyrl-ID-1901-BOKMAL-u-ca-japanese"));
        Assert.assertEquals(
                "nb-Cyrl-ID-xsistemo-u-ca-japanese",
                canonicalTag("no-Cyrl-ID-BOKMAL-xsistemo-u-ca-japanese"));

        Assert.assertEquals("nn", canonicalTag("no-NYNORSK"));
        // also test with script, country and extensions
        Assert.assertEquals(
                "nn-Cyrl-ID-u-ca-japanese", canonicalTag("no-Cyrl-ID-NYNORSK-u-ca-japanese"));

        Assert.assertEquals("ssy", canonicalTag("aa-SAAHO"));
        // also test with script, country and extensions
        Assert.assertEquals(
                "ssy-Devn-IN-u-ca-japanese", canonicalTag("aa-Devn-IN-SAAHO-u-ca-japanese"));

        // language -> language
        Assert.assertEquals("aas", canonicalTag("aam"));
        // also test with script, country, variants and extensions
        Assert.assertEquals(
                "aas-Cyrl-ID-3456-u-ca-japanese", canonicalTag("aam-Cyrl-ID-3456-u-ca-japanese"));

        // language -> language _ Script
        Assert.assertEquals("sr-Latn", canonicalTag("sh"));
        // also test with script
        Assert.assertEquals("sr-Cyrl", canonicalTag("sh-Cyrl"));
        // also test with country, variants and extensions
        Assert.assertEquals("sr-Latn-ID-3456-u-ca-roc", canonicalTag("sh-ID-3456-u-ca-roc"));

        // language -> language _ country
        Assert.assertEquals("fa-AF", canonicalTag("prs"));
        // also test with country
        Assert.assertEquals("fa-RU", canonicalTag("prs-RU"));
        // also test with script, variants and extensions
        Assert.assertEquals("fa-Cyrl-AF-1009-u-ca-roc", canonicalTag("prs-Cyrl-1009-u-ca-roc"));

        Assert.assertEquals("pa-IN", canonicalTag("pa-IN"));
        // also test with script
        Assert.assertEquals("pa-Latn-IN", canonicalTag("pa-Latn-IN"));
        // also test with variants and extensions
        Assert.assertEquals("pa-IN-5678-u-ca-hindi", canonicalTag("pa-IN-5678-u-ca-hindi"));

        Assert.assertEquals("ky-Cyrl-KG", canonicalTag("ky-Cyrl-KG"));
        // also test with variants and extensions
        Assert.assertEquals("ky-Cyrl-KG-3456-u-ca-roc", canonicalTag("ky-Cyrl-KG-3456-u-ca-roc"));

        // Test replacement of scriptAlias
        Assert.assertEquals("en-Zinh", canonicalTag("en-Qaai"));

        // Test replacement of territoryAlias
        // 554 has one replacement
        Assert.assertEquals("en-NZ", canonicalTag("en-554"));
        Assert.assertEquals("en-NZ-u-nu-arab", canonicalTag("en-554-u-nu-arab"));

        // 172 has multiple replacements
        // also test with variants
        Assert.assertEquals("ru-RU-1234", canonicalTag("ru-172-1234"));
        // also test with variants
        Assert.assertEquals("ru-RU-1234-u-nu-latn", canonicalTag("ru-172-1234-u-nu-latn"));
        Assert.assertEquals("uz-UZ", canonicalTag("uz-172"));
        // also test with scripts
        Assert.assertEquals("uz-Cyrl-UZ", canonicalTag("uz-Cyrl-172"));
        Assert.assertEquals("uz-Bopo-UZ", canonicalTag("uz-Bopo-172"));
        // also test with variants and scripts
        Assert.assertEquals(
                "uz-Cyrl-UZ-5678-u-nu-latn", canonicalTag("uz-Cyrl-172-5678-u-nu-latn"));
        // a language not used in this region
        Assert.assertEquals("fr-RU", canonicalTag("fr-172"));

        Assert.assertEquals("ja-Latn-alalc97", canonicalTag("ja-Latn-hepburn-heploc"));

        Assert.assertEquals("aaa-Fooo-RU", canonicalTag("aaa-Fooo-SU"));

        // ICU-21344
        Assert.assertEquals("ku-Arab-IQ", canonicalTag("ku-Arab-NT"));

        // ICU-21402
        Assert.assertEquals("und-u-rg-no50", canonicalTag("und-u-rg-no23"));
        Assert.assertEquals("und-u-rg-cnbj", canonicalTag("und-u-rg-cn11"));
        Assert.assertEquals("und-u-rg-cz110", canonicalTag("und-u-rg-cz10a"));
        Assert.assertEquals("und-u-rg-frges", canonicalTag("und-u-rg-fra"));
        Assert.assertEquals("und-u-rg-frges", canonicalTag("und-u-rg-frg"));
        Assert.assertEquals("und-u-rg-lucl", canonicalTag("und-u-rg-lud"));

        Assert.assertEquals("und-NO-u-sd-no50", canonicalTag("und-NO-u-sd-no23"));
        Assert.assertEquals("und-CN-u-sd-cnbj", canonicalTag("und-CN-u-sd-cn11"));
        Assert.assertEquals("und-CZ-u-sd-cz110", canonicalTag("und-CZ-u-sd-cz10a"));
        Assert.assertEquals("und-FR-u-sd-frges", canonicalTag("und-FR-u-sd-fra"));
        Assert.assertEquals("und-FR-u-sd-frges", canonicalTag("und-FR-u-sd-frg"));
        Assert.assertEquals("und-LU-u-sd-lucl", canonicalTag("und-LU-u-sd-lud"));

        // ICU-21550
        Assert.assertEquals("und-u-rg-axzzzz", canonicalTag("und-u-rg-fi01"));
        Assert.assertEquals("und-u-rg-cpzzzz", canonicalTag("und-u-rg-frcp"));
        Assert.assertEquals("und-u-rg-pmzzzz", canonicalTag("und-u-rg-frpm"));
        Assert.assertEquals("und-u-rg-vizzzz", canonicalTag("und-u-rg-usvi"));
        Assert.assertEquals("und-u-rg-hkzzzz", canonicalTag("und-u-rg-cn91"));
        Assert.assertEquals("und-u-rg-awzzzz", canonicalTag("und-u-rg-nlaw"));

        Assert.assertEquals("und-NO-u-sd-rezzzz", canonicalTag("und-NO-u-sd-frre"));
        Assert.assertEquals("und-CN-u-sd-cwzzzz", canonicalTag("und-CN-u-sd-nlcw"));
        Assert.assertEquals("und-CZ-u-sd-guzzzz", canonicalTag("und-CZ-u-sd-usgu"));
        Assert.assertEquals("und-FR-u-sd-tazzzz", canonicalTag("und-FR-u-sd-shta"));
        Assert.assertEquals("und-FR-u-sd-twzzzz", canonicalTag("und-FR-u-sd-cn71"));

        // ICU-21401
        Assert.assertEquals("xtg", canonicalTag("cel-gaulish"));

        // ICU-21406
        // Inside T extension
        //  Case of Script and Region
        Assert.assertEquals("ja-Kana-JP-t-it-latn-it", canonicalTag("ja-kana-jp-t-it-latn-it"));
        Assert.assertEquals("und-t-zh-hani-tw", canonicalTag("und-t-zh-hani-tw"));
        Assert.assertEquals("und-Cyrl-t-und-latn", canonicalTag("und-cyrl-t-und-Latn"));
        //  Order of singleton
        Assert.assertEquals("und-t-zh-u-ca-roc", canonicalTag("und-u-ca-roc-t-zh"));
        //  Variant subtags are alphabetically ordered.
        Assert.assertEquals("sl-1994-biske-rozaj", canonicalTag("sl-rozaj-biske-1994"));
        Assert.assertEquals("sl-t-sl-1994-biske-rozaj", canonicalTag("sl-t-sl-rozaj-biske-1994"));
        // tfield subtags are alphabetically ordered.
        // (Also tests subtag case normalisation.)
        Assert.assertEquals("de-t-lv-m0-din", canonicalTag("DE-T-lv-M0-DIN"));
        Assert.assertEquals("de-t-k0-qwertz-m0-din", canonicalTag("DE-T-M0-DIN-K0-QWERTZ"));
        Assert.assertEquals("de-t-lv-k0-qwertz-m0-din", canonicalTag("DE-T-lv-M0-DIN-K0-QWERTZ"));
        // "true" tvalue subtags aren't removed.
        // (UTS 35 version 36, §3.2.1 claims otherwise, but tkey must be followed by
        // tvalue, so that's likely a spec bug in UTS 35.)
        Assert.assertEquals("en-t-m0-true", canonicalTag("en-t-m0-true"));
        // tlang subtags are canonicalised.
        Assert.assertEquals("en-t-he", canonicalTag("en-t-iw"));
        Assert.assertEquals("en-t-hy-latn-am", canonicalTag("en-t-hy-latn-SU"));
        Assert.assertEquals("ru-t-ru-cyrl-ru", canonicalTag("ru-t-ru-cyrl-SU"));
        Assert.assertEquals("fr-t-fr-ru", canonicalTag("fr-t-fr-172"));
        Assert.assertEquals("und-t-nb-latn", canonicalTag("und-t-no-latn-BOKMAL"));
        Assert.assertEquals("und-t-dse-zinh", canonicalTag("und-t-sgn-qAAi-NL"));
        // alias of tvalue should be replaced
        Assert.assertEquals("en-t-m0-prprname", canonicalTag("en-t-m0-NaMeS"));
        Assert.assertEquals("en-t-d0-charname-s0-ascii", canonicalTag("en-t-s0-ascii-d0-nAmE"));
    }

    @Test
    public void TestVariantLengthWithinLimit() {
        String valid =
                "_"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678";

        ULocale locale = new ULocale(valid);
        Assert.assertEquals(valid.substring(2), locale.getVariant());
    }

    @Test(expected = IllegalArgumentException.class)
    public void TestVariantLengthOverLimit() {
        String invalid =
                "_"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678"
                        + "_12345678X"; // One character too long.
        ULocale locale = new ULocale(invalid);
    }

    @Test
    public void TestLocaleCanonicalizationFromFile() throws IOException {
        BufferedReader testFile =
                TestUtil.getUtf8DataReader("cldr/localeIdentifiers/localeCanonicalization.txt");
        try {
            String line;
            while ((line = testFile.readLine()) != null) {
                if (line.startsWith("#")) {
                    // ignore any lines start with #
                    continue;
                }
                String[] fields = line.split("\t;\t");
                if (fields.length != 2) {
                    // ignore any lines without TAB ; TAB
                    continue;
                }
                String from = fields[0].replace("_", "-");
                String to = fields[1].replace("_", "-");
                Assert.assertEquals("canonicalTag(" + from + ")", to, canonicalTag(from));
            }
        } finally {
            testFile.close();
        }
    }

    private static final class TestCase implements Cloneable {
        private static final String ENDL = System.getProperties().getProperty("line.separator");

        int lineNr = 0;

        String source = "";
        String addLikely = "";
        String removeFavorScript = "";
        String removeFavorRegion = "";

        @Override
        public TestCase clone() throws CloneNotSupportedException {
            return (TestCase) super.clone();
        }

        @Override
        public String toString() {
            return (new StringBuilder(source))
                    .append(";")
                    .append(addLikely)
                    .append(";")
                    .append(removeFavorScript)
                    .append(";")
                    .append(removeFavorRegion)
                    .toString();
        }
    }

    static List<TestCase> readLikelySubtagsTestCases() throws Exception {
        List<TestCase> tests = new ArrayList<>();
        TestCase test = new TestCase();
        BufferedReader testFile =
                TestUtil.getUtf8DataReader("cldr/localeIdentifiers/likelySubtags.txt");
        try {
            String line;
            while ((line = testFile.readLine()) != null) {
                if (line.startsWith("#")) continue;
                String[] fields = line.split("[ \t]?;[ \t]?");
                if (fields.length < 2) continue;
                test.source = fields[0];
                test.addLikely = fields[1];
                test.removeFavorScript =
                        (fields.length < 3) || fields[2].isEmpty() ? test.addLikely : fields[2];
                test.removeFavorRegion =
                        (fields.length < 4) || fields[3].isEmpty()
                                ? test.removeFavorScript
                                : fields[3];
                tests.add(test.clone());
            }
        } finally {
            testFile.close();
        }
        return tests;
    }

    // ICU-22742, test addLikelySubtags with pseudo-locales
    @Test
    public void TestPseudoLocales() {
        // input locale tag, expected locale tag
        String[][] testCases =
                new String[][] {
                    // language + region, en
                    {"en-XA", "en-Latn-XA"},
                    {"en-XB", "en-Latn-XB"},
                    {"en-XC", "en-Latn-XC"},

                    // language + region, ar
                    {"ar-XA", "ar-Arab-XA"},
                    {"ar-XB", "ar-Arab-XB"},
                    {"ar-XC", "ar-Arab-XC"},

                    // language + region, something other than en, ar
                    {"ru-XA", "ru-Cyrl-XA"},
                    {"el-XB", "el-Grek-XB"},

                    // undefined language - region
                    {"und-XA", "en-Latn-XA"},
                    {"und-XB", "en-Latn-XB"},
                    {"und-XC", "en-Latn-XC"},

                    // language + script + region
                    {"und-Latn-XA", "en-Latn-XA"},
                    {"und-Latn-XB", "en-Latn-XB"},
                    {"und-Latn-XC", "en-Latn-XC"},
                    {"und-Arab-XA", "ar-Arab-XA"},
                    {"und-Arab-XB", "ar-Arab-XB"},
                    {"und-Arab-XC", "ar-Arab-XC"},
                    {"und-Cyrl-XA", "ru-Cyrl-XA"},
                    {"und-Grek-XB", "el-Grek-XB"},

                    // Make sure the script is not damaged, when correct
                    {"ru-Cyrl-XA", "ru-Cyrl-XA"},
                    {"el-Grek-XB", "el-Grek-XB"},

                    // Make sure the script is not damaged, even if it is wrong
                    {"ru-Grek-XA", "ru-Grek-XA"},
                    {"el-Cyrl-XB", "el-Cyrl-XB"},

                    // PS Variants
                    {"en-XA-PSACCENT", "en-Latn-XA-psaccent"},
                    {"en-XA-PSBIDI", "en-Latn-XA-psbidi"},
                    {"en-XA-PSCRACK", "en-Latn-XA-pscrack"},
                    {"ar-XB-PSACCENT", "ar-Arab-XB-psaccent"},
                    {"ar-XB-PSBIDI", "ar-Arab-XB-psbidi"},
                    {"ar-XB-PSCRACK", "ar-Arab-XB-pscrack"},
                    {"en-XC-PSACCENT", "en-Latn-XC-psaccent"},
                    {"en-XC-PSBIDI", "en-Latn-XC-psbidi"},
                    {"en-XC-PSCRACK", "en-Latn-XC-pscrack"},
                    {"en-US-PSACCENT", "en-Latn-US-psaccent"},
                    {"en-US-PSBIDI", "en-Latn-US-psbidi"},
                    {"en-US-PSCRACK", "en-Latn-US-pscrack"},
                    {"ar-EG-PSACCENT", "ar-Arab-EG-psaccent"},
                    {"ar-EG-PSBIDI", "ar-Arab-EG-psbidi"},
                    {"ar-EG-PSCRACK", "ar-Arab-EG-pscrack"},
                    {"en-PSACCENT", "en-Latn-US-psaccent"},
                    {"en-PSBIDI", "en-Latn-US-psbidi"},
                    {"en-PSCRACK", "en-Latn-US-pscrack"},
                    {"ar-PSACCENT", "ar-Arab-EG-psaccent"},
                    {"ar-PSBIDI", "ar-Arab-EG-psbidi"},
                    {"ar-PSCRACK", "ar-Arab-EG-pscrack"},
                    {"und-US-PSACCENT", "en-Latn-US-psaccent"},
                    {"und-US-PSBIDI", "en-Latn-US-psbidi"},
                    {"und-US-PSCRACK", "en-Latn-US-pscrack"},
                    {"und-EG-PSACCENT", "ar-Arab-EG-psaccent"},
                    {"und-EG-PSBIDI", "ar-Arab-EG-psbidi"},
                    {"und-EG-PSCRACK", "ar-Arab-EG-pscrack"},
                    {"und-PSACCENT", "en-Latn-US-psaccent"},
                    {"und-PSBIDI", "en-Latn-US-psbidi"},
                    {"und-PSCRACK", "en-Latn-US-pscrack"},
                    {"und-PSACCENT", "en-Latn-US-psaccent"},
                    {"und-PSBIDI", "en-Latn-US-psbidi"},
                    {"und-PSCRACK", "en-Latn-US-pscrack"},
                };
        String extensions = "-u-nu-Deva-hc-h23-fw-mon-mu-celsius-x-something-more";

        for (String[] testCase : testCases) {
            String inputTag = testCase[0];
            String expectedTag = testCase[1];
            ULocale result = ULocale.addLikelySubtags(ULocale.forLanguageTag(inputTag));
            ULocale expected = ULocale.forLanguageTag(expectedTag);
            assertEquals("pseudo-locales(" + inputTag + ")", expected, result);

            // Make sure this also works with extensions. Kind of hacky...
            result = ULocale.addLikelySubtags(ULocale.forLanguageTag(inputTag + extensions));
            expected = ULocale.forLanguageTag(expectedTag + extensions);
            assertEquals("pseudo-locales(" + inputTag + ")", expected, result);
        }
    }

    @Test
    @Parameters(method = "readLikelySubtagsTestCases")
    public void likelySubtagsDataDriven(TestCase test) {
        ULocale l = ULocale.forLanguageTag(test.source);

        if (test.addLikely.equals("FAIL")) {
            assertEquals(
                    "addLikelySubtags(" + test.source + ") should be unchanged",
                    l,
                    ULocale.addLikelySubtags(l));
        } else {
            assertEquals(
                    "addLikelySubtags(" + test.source + ")",
                    test.addLikely,
                    ULocale.addLikelySubtags(l).toLanguageTag());
        }
        if (test.removeFavorRegion.equals("FAIL")) {
            assertEquals(
                    "minimizeSubtags(" + test.source + ") should be unchanged",
                    l,
                    ULocale.minimizeSubtags(l));
        } else {
            assertEquals(
                    "minimizeSubtags(" + test.source + ")",
                    test.removeFavorRegion,
                    ULocale.minimizeSubtags(l).toLanguageTag());
        }
        if (test.removeFavorScript.equals("FAIL")) {
            assertEquals(
                    "minimizeSubtags(" + test.source + ") - FAVOR_SCRIPT should be unchanged",
                    l,
                    ULocale.minimizeSubtags(l, ULocale.Minimize.FAVOR_SCRIPT));
        } else {
            assertEquals(
                    "minimizeSubtags(" + test.source + ") - FAVOR_SCRIPT",
                    test.removeFavorScript,
                    ULocale.minimizeSubtags(l, ULocale.Minimize.FAVOR_SCRIPT).toLanguageTag());
        }
    }
}
